Skip to content
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

Service worker doesn't work, what's next? #130

Open
iVanlIsh opened this issue Mar 12, 2024 · 6 comments
Open

Service worker doesn't work, what's next? #130

iVanlIsh opened this issue Mar 12, 2024 · 6 comments

Comments

@iVanlIsh
Copy link
Collaborator

We would like to propose that websites needs fetch() to unlock mixed content fetches in local/private address space.

Once fetch() is sent with targetAddressSpace for a certain domain. It will cached for the current document context and it will be used to enable preflights to the same domain in other contexts, e.g. <iframe> or <img>.

@DanielBaulig
Copy link

DanielBaulig commented Mar 12, 2024

I don't currently have a strong opinion on this, but would like to share some general thoughts / questions that might need consideration.

  • What's the primary objective of this proposal? What is the alternative? I assume it's to enable PNA via other APIs while reducing the API surface and thus required changes in web specifications. But knowing for certain what the objective is might make the feedback more useful and practical.
  • Is "unlocking" like this a pattern used anywhere elsewhere in the web platform? There might be lessons to be learned from that, if so.
  • Would this "unlocking" apply to all other APIs or just HTML elements in the document?
  • This should be obvious, but worth calling out nevertheless: this will force the application developer to send a (potentially arbitrary and useless) fetch request before rendering static HTML utilizing private or local address space resources (e.g. <img> or <iframe>). This might make something that should be easy and trivial, more convoluted and complicated (e.g. rendering a static HTML website).
  • What is the proposed behavior regarding concurrency? Will the application developer have to wait for the fetch request to complete before rendering any of the HTML or using other APIs utilizing private or local address space resources? Or is just having made a call to the fetch API sufficient (e.g. a <script> tag at the beginning of the document doing a targetAddressSpace fetch will "unlock" the address space for any subsequent statically rendered <img> and <iframe> tags pointing at the same origin)?
  • Sending an "ahead of time" fetch request might be expensive. I think one practical example of this is the IOT device application. The remote endpoint might not be a particularly powerful computing device and it might impose noteworthy costs to serve the ahead of time fetch request, in compute, memory, battery, etc. For example many IOT devices are so low in memory and compute that they might struggle with serving multiple HTTP responses concurrently without exhausting CPU and/or memory leading to slow downs or even OOM crashes.
  • Sending an "ahead of time" fetch request might not be possible to do side effect free in all conceivable circumstances. I can't come up with any good practical examples that don't seem entirely contrived -- and as such this might be more of a theoretical issue -- but if all potential endpoints to send a fetch request to could have side effects, then "unlocking" other API surfaces might effectively not be possible. E.g. unlocking for a <form> element posting to a singular endpoint triggering a side effect.
  • This could potentially compromise security. "unlocking" private and local network address access to a given origin globally for the entire document will significantly increase the potential attack surface available to an attacker. For example, if a single statically rendered <form> element needs access to a private address space origin, unlocking access to that private address space origin for all elements might allow an attacker to effectively exploit an otherwise benign injection attack. Think a markdown image pointing at ![You're p0wned](http://192.168.0.1/restart.cgi) which will restart your router every time your browser loads this image as part of the document that unlocked PNA to support aforementioned <form> element.

I think one way to resolve many (but not all) issues would be to provide an API to "unlock" an origin for PNA requests without having to actually send a request. Bonus points if that API can be used from static HTML. I think an earlier proposed CSP would have done exactly that. Was that idea discarded? If so, why?

Edit: The title of the post seems to imply that this proposed change is related to the current ServiceWorker limitations. It's unclear to me how those two things are related. Some elaboration would be greatly appreciated :)

@iVanlIsh
Copy link
Collaborator Author

Why we cannot use service worker: The mixed content check is triggered before the requests go into service worker, so that the changes adding inside service worker, like adding targetAddressSpace, cannot be seen by the mixed content checker.

The word "unlock" might be not that much accurate. Our current idea is when one "fetch()" has been triggered with targetAddressSpace private/local for a certain host. This targetAddressSpace will be cached and applied to requests ( e.g. for HTML or <iframe> ) to the same host afterwards. The preflight will still be sent.

One pros for this is, the website now have chance to control when the permission prompt will be shown.

@annevk
Copy link

annevk commented Mar 13, 2024

Wouldn't that bypass one of the CORS design aspects of only guaranteeing things for a given URL, not for a set of paths?

I'm also somewhat surprised that we now have fetch() triggering UI. For all the places fetch used to do that it has typically been deemed a mistake. It also seems like a layering violation and given the timing around network fetches will likely not lead to a good user experience (it would also be harder to require the user to have prior interaction and such).

@DanielBaulig
Copy link

DanielBaulig commented Mar 13, 2024

The word "unlock" might be not that much accurate. Our current idea is when one "fetch()" has been triggered with targetAddressSpace private/local for a certain host. This targetAddressSpace will be cached and applied to requests ( e.g. for HTML or <iframe> ) to the same host afterwards. The preflight will still be sent

Yes, that was my understanding crafting the response. "Unlock" was probably a poorly chosen word. I do think that all concerns / thoughts raised in my response are valid and applicable to this scenario though.

One pros for this is, the website now have chance to control when the permission prompt will be shown.

Could you elaborate a little more on this aspect? It doesn't appear obvious to me why that would be the case.
You mean compared to the CSP solution or compared to having to specify targetAddressSpace explicitly on each fetch / API call? For the former it seems like the actual UI would only trigger as part of the actual request (timing would be virtually equal). Not to mention that it would be possible to add the meta elements just before sending the request anyway. In the latter case it should be obvious that there is much more granular control about when and to which requests to apply PNA.

I would still like to understand how this proposal differs from the CSP proposal and why you believe this would be the better approach.

Why we cannot use service worker: The mixed content check is triggered before the requests go into service worker, so that the changes adding inside service worker, like adding targetAddressSpace, cannot be seen by the mixed content checker.

Thank you for clarifying. This was my understanding. It isn't obvious to me how this proposal will help with that. Or am I confused and this proposal is entirely unrelated to the service worker issue?

It is also unclear to me how that particular behavior is a problem in of itself. If the application believes a given URL is in the private or local address space, it should apply the correct targetAddressSpace option to the request within the main application context. The mixed content checker can then perform it's check accordingly. As far as I understand it is not possible for a service worker to make changes to the URL or targetAddressSpace of that particular request after the fact. A service worker can choose to issue a different request in response to the fetch event, but that would be a new request with a new (and potentially different) URL, a new options bag and it's own mixed content check. It would be up to the service worker author to correctly apply the targetAddressSpace option again if they believe the request to be going to a private or local address space origin. This would require PNA/PermissionDialog support within ServiceWorker though, which I if I am not mistaken is at this point not implemented.

I think I now understand: What you are saying is that it's not possible to use service worker as a solution to the problem of how to add targetAddressSpace to non fetch()-API fetch requests. The reason being because a mixed content check will get applied to them before the service worker fetch event triggers and the service worker has the opportunity to re-issue a new request with the correct targetAddressSpace option set. That makes a lot of sense.

It seems like the most comprehensive approach would be to move the mixed content check to "before the request goes onto the wire", i.e. to the time of egress from the service worker. I'm not going to pretend I understand all of the implications (e.g risks and effort) of such a change, but it appears that the mixed content policy is intended to prevent insecure requests from unknowingly to the user traversing networks where they could be intercepted, observed, manipulated, etc. As long as a request doesn't traverse a network, it is not subject to those threats. A request to http://somedomain.com passing through a service worker should not be subject to the mixed content policy before it is about to leave the service worker and go onto the wire/air. All mixed content checks should happen at the time of egress from the service worker, not at the time of (or before) ingress.

At a time before service workers it may have made sense to do the mixed content check at the time of request creation, but today it is not a given that a request will actually end up on a wire just because it was instantiated. Moving the mixed content check to the time of or just before egress seems to be the "correct" solution here.

This would also enable service workers to provide responses to insecure requests for which they may have obtained a response previously or via a way different than fetch() without opening the application up to potentially sending an insecure request onto the wire.

E.g. in a world with egress-timed mixed content checks, the following would be possible:

// in application.js
// We would like our SW to handle this request, but never accidentally send it onto the wire
fetch('http://192.168.0.1' /* no targetAddressSpace set */)

// in sw.js
addEventListener('fetch', (event) => {
  if (isPrivateAddressSpace(event.request.url)) {
    return event.respondWith(respondSomehowNotUsingFetch(event.request))
  }
  // Even in the case of a request slipping through our isPrivateAddressSpace implementation,
  // the mixed content check would still prevent it or any PNA preflight requests from going
  // onto the wire.
});

@DanielBaulig
Copy link

DanielBaulig commented Mar 13, 2024

Ok, here's another thought: If we assumed the mixed-content policy wasn't a problem, would service worker then be considered a reasonable solution to the problem? Because if so I don't think it is even necessary to come up with a solution as part of the specification. Once PNA/PermissionDialog is implemented in service workers, the following might be a reasonable application based solution:

// in application.js
// Note that the request is using a secure scheme within the main application context
fetch('https://192.168.0.1')
// Would work equally with HTML elements and other APIs
// <img src="https://192.168.0.1/img.png" />

// in sw.js
addEventListener('fetch', (event) => {
  if (needsInsecureRedirection(event.request.url)) {
    // Replace the scheme with an insecure scheme and set targetAddressSpace accordingly
    return event.respondWith(fetch(makeInsecureScheme(event.request.url), { targetAddressSpace: 'private' } ));
  }
});

I recognize that this puts the onus of being able to identify and manage URIs that should be redirected to insecure endpoints onto the application developer, but that doesn't seem too crazy. A simple scheme could be put in place by the application developer to encode that information in the URI itself (e.g. 'https://192.168.0.1?makeInsecure=1') and transform the URI accordingly in the service worker. I still think egress-timing the mixed content check is probably the more comprehensive solution, but from skimming through the fetch, Service Worker and Mixed Content specs for a bit I also recognize that that might not be an exactly trivial undertaking.

In fact this is what I naturally attempted on doing to get PNA/PermissionDialog to work with EventSource in my project, but had to instead go with a fetch()-based EventSource polyfill when I realized I couldn't send PNA/PermissionDialog fetch() requests from Service Worker yet.

@iVanlIsh
Copy link
Collaborator Author

targetAddressSpace is made for mixed content check and it seems hard and unworthy to change the current (mixed content spec)[https://www.w3.org/TR/mixed-content/].

Content Security Policy option got some opposition internally because on one hand, we have put too many random stuff into CSP and it is probably not good to do it more, and on the other hand, targetAddressSpace don't actually meet the CSP policy as it is generally meant to enable a document to restrict the locations of external resources it can load. So we are thinking of alternative way for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants