-
Notifications
You must be signed in to change notification settings - Fork 15k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: net.fetch() supports custom protocols #36606
Conversation
Let's make sure |
I would argue for safety and to avoid a common security footgun to simply prevent usage of TLDR: This seems dangerous, can we make it safe by default? |
Can you tell me more about the threat model you're imagining? I feel like putting arbitrary user-controlled URLs into |
I'm not sure specifically, but I don't think fetching http/s user controlled URLs and doing something with the result is inherently unsafe. But allowing those URLs to transparently be |
I think it really depends on what "doing something with the result" is. For example, even if the response data is completely ignored, if an attacker controls an http request completely, that allows the attacker to:
I'd say that these are already enough to warrant careful attention to controlling the URLs that are fed to From a different angle, let me give a little background about why I want to make this change. I am planning a refactor of the protocol.handle('https', async (req) => {
if (req.url === 'https://foo.com/replaced') {
return '<body>this is some replacement content</body>'
} else {
// Need to specify skipCustomHandlers to avoid recursing.
const r = net.request({...req, skipCustomHandlers: true}).end()
const response = await new Promise(resolve => req.on('response', resolve))
response.headers['New-Header'] = 'New-Header-Value'
return response
}
}) This is more flexible and more powerful than the existing APIs, in that:
Ideally the underlying system would be clever enough to avoid going through JS for each data chunk, and this would have roughly similar performance to an unintercepted request that was manipulated by So that's the motivation. (Now that I write this out, I'm also seeing that we'll need to make sure that redirects to other schemes are rejected by default, as they are in the browser.) Perhaps you can think of other ways to achieve these goals? |
Just at a glance your proposed API isn't super easy to use, especially around error handling, processing of request bodies, etc. I don't know how technically complex this API design would be but I think the nicest way of handling your problem space from an API perspective would be this. (Some objects filled for completeness but some properties should be optional) protocol.handle('https', async (req) => {
if (req.url === 'https://foo.com/replaced') {
return {
headers: {}, // optional
statusCode: 200, // optional
body: '<body>this is some replacement content</body>'
};
} else {
const response = await req.passthrough();
response.headers['New-Header'] = 'New-Header-Value'
return response
}
})
protocol.handle('my-app', async (req) => {
const response = await req.rewrite({
headers: {}, // optional
body: 'new-body', // optional
url: 'file:///etc/foo.conf,
});
response.headers['New-Header'] = 'New-Header-Value'
return response
}) Whether this is implemented by custom magic flags to the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good work
@MarshallOfSound hm, I agree the What about introducing a self.addEventListener("fetch", (event) => {
event.respondWith(
(async () => {
const cachedResponse = await cache.match(event.request);
if (cachedResponse)
return cachedResponse;
return fetch(event.request);
})()
);
}); I don't think we need the We could do something similar: protocol.handle('https', (request) => {
if (request.url === 'https://thing')
return '<body>custom</body>'
return net.fetch(request)
}) or, to forward to a different URL, protocol.handle('my-custom-scheme', (request) => {
if (request.url === 'to-http')
return net.fetch('https://my-server.com/request')
}) This perhaps addresses the security concern, too: we wouldn't be expanding the attack surface of an existing API that's in use by apps; the existing |
Generically 👍 on the idea of Things to keep in mind:
|
|
Ref #36733, it looks like we can probably reuse a bunch of Node's work here and just swap out the actual network stack. |
I'm going to come back to this idea once |
funny story, the |
I've changed this PR to limit usage of non-http schemes to the new |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need any documentation changes for this, or is that already covered by the PR to add net.fetch
?
API LGTM |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
API LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why the x64 and ia32 Windows tests passed, but the Windows on Arm test failure is legitimate due to different line endings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
API LGTM
CI failure is flake; merging. |
Release Notes Persisted
|
Description of Change
Allow net.fetch() to request protocols other than
http:
andhttps:
,including
file:
and custom protocols.Notably this doesn't support
chrome-extension:
orchrome:
URLs, nor does itallow differentiating between various different modes of URL loading
(navigation, subresource, worker main resource, service worker update) which
Chromium does differentiate internally.
This is also duplicating logic that exists elsewhere and we likely need to
clean this up. There are several considerations that need to be made when
requesting a URL in Electron:
webRequest
handler that needs to be invoked?chrome.webRequest
handler? (This is not thesame thing as
webRequest
.)protocol.register*Protocol
)?protocol.interceptProtocol
)?file:
be allowed? (We do some hacks here to allow service workers to be loaded in file:// domains; arguably we should not do this)The logic for handling these are spread around the code.
ProxyingURLLoaderFactory
handles thewebRequest
API and intercepted protocols, as well as ignoring connection limits for certain requests.RegisterNonNetwork*
methods of ElectronBrowserClient. These methods also make decisions about where to allowfile:
access and where not to, based on the specific method.InspectableWebContents::LoadNetworkResource
to support devtools. NB that code doesn't currently useProxyingURLLoaderFactory
so it doesn't supportwebRequest
or intercepted protocols.file:
urls insideProxyingURLLoaderFactory
.Ideally I think it should be possible to use
net.fetch
to produce areasonably good facsimile of any request that a renderer could generate. This
gets us a little bit closer to that ideal.
Checklist
npm test
passesRelease Notes
Notes: Changed
net.fetch
to support requests tofile:
URLs and custom protocols registered withprotocol.register*Protocol
.