privacycg / storage-access

The Storage Access API
https://privacycg.github.io/storage-access/
199 stars 26 forks source link

How does storage access interact with Dedicated, Shared and Service Workers? #157

Open johannhof opened 1 year ago

johannhof commented 1 year ago

If a Shared Worker makes a network request, can it ever consider storage access to include cross-site cookies?

@annevk sketched out the following scenario that might make this tricky:

We have two tabs A1 and A2. A1 embeds cross-site B1 and A2 embeds cross-site B2.

B1 requests and is granted storage access, but B2 does not and thus is still fully partitioned, including its cookies.

B1 and B2 now create the same SharedWorker. On that SharedWorker, does the environment have storage access? Depending on a race it could either have been created by B1 or B2. When it makes a network fetch that is observable by both B1 and B2.

johannhof commented 1 year ago

cc @dcthetall

johannhof commented 10 months ago

@wanderview do you happen to have any thoughts on this? Anyone else come to mind who may have an opinion here? :)

annevk commented 10 months ago

Copying the "has storage access" bit from the document that creates the shared worker seems the easiest, but it also creates a race that people tried to avoid when designing shared workers.

@smaug---- @wanderview @hiroshige-g @gterzian @elkurin @domenic your input would be much appreciated!

smaug---- commented 10 months ago

I would expect at least different SharedWorkers to be created depending on whether one has storage access when creating it. Not sure about ServiceWorkers

annevk commented 10 months ago

Hmm, why would you expect that? The partition does not change and BroadcastChannel would also not behave differently (at least as things are agreed upon per https://privacycg.github.io/storage-partitioning/). This solely gives you access to different cookies.

smaug---- commented 10 months ago

Because of https://privacycg.github.io/storage-access/#storage "A future revision of this API might impact other client-side state"

wanderview commented 10 months ago

In principle any permission a document connected to the SharedWorker is accessible to the SharedWorker via postMessage(). So I think inheriting the access bit is somewhat reasonable.

In theory SAA grants storage access in a semi-persistent way (for some time period). So the idea you would have one A->B window with access and another A->B without access both connected to the same B SharedWorker seems to only affect niche use cases. Like when first getting access or it expiring.

Maybe rather than inheriting it should look for semi-persistent permission when creating the SharedWorker. Look at the SW's storage key to determine if a permission grant exists or not. This functions the same as inheriting in that use case, but might be a bit more predictable for devs.

Its possible I am missing context and misunderstood, though.

annevk commented 10 months ago

We have per-document grants somewhat intentionally so inheriting from something more global feels wrong.

@arturjanc I think the threat analysis for Storage Access didn't cover workers, but maybe it should? Would be helpful here.

wanderview commented 10 months ago

Sorry, I was not aware of the per-document restriction.

If that is the case, how is SharedWorker different from two documents connected via BroadcastChannel?

That would seem to argue not to inherit?

annevk commented 10 months ago

I suppose we could add some kind of requestStorageAccess() API to shared/service workers, which would only work if you had prior permission in some document. That would probably be the cleanest option, though to what extent it's web compatible remains to be seen.

johannhof commented 9 months ago

Hmm given the back and forth here I wonder if we should highlight this for the SAA graduation discussion at TPAC!

bvandersloot-mozilla commented 9 months ago

My two cents:

There are two cases that we can think about: now and eventually.

@smaug---- 's comments seem to be in the "eventually" case, and I think are based on the what seems natural to me: inherit the partition of the Document on creation. (correct me if I'm wrong). However, I do see potential security benefit of forcing the worker to explicitly request storage access before having unpartitioned cookies, although I haven't thought it through yet.

For the "now" case, we are more flexible. The spec currently dictates cookie-only, however it seems possible to skip straight to the "eventually" case since this seems different than the existing discussion of Storage and Storage Access.

johannhof commented 9 months ago

Any thoughts on dedicated workers? @cfredric and I got a bug report that Chrome currently isn't unblocking fetches done in those, while Safari is. I don't think this is something that we're currently specifying, although we probably should (feels like part of cookie layering though). Overall it seems non-controversial to say that if the context that spawned the dedicated worker had storage access, it should propagate to the worker. What about existing workers though, should these be "upgraded" once their existing document gets storage access?

bvandersloot-mozilla commented 9 months ago

What about existing workers though, should these be "upgraded" once their existing document gets storage access?

My intuition is no- having a worker change network partitions mid-execution due to no action of its own sounds apt to cause more confusion than it is worth.

annevk commented 9 months ago

Overall it seems non-controversial to say that if the context that spawned the dedicated worker had storage access, it should propagate to the worker.

It's somewhat bad, as we don't propagate any other state that way currently, except for blob: and data: URL workers. But if we need to for compatibility, it's workable.

I'm not sure what to do about existing workers. If we don't make those work we encourage sites to instantiate workers after getting storage access, which makes them targetable through XSLeaks (using global resource exhaustion).


For shared/service workers, is there a test so we can see what implementations do today? I think ideally for those we require an API call that only works if permission was already granted for the relevant permission key.

johannhof commented 9 months ago

For shared/service workers, is there a test so we can see what implementations do today? I think ideally for those we require an API call that only works if permission was already granted for the relevant permission key.

A requestStorageAccess variant for shared/service workers seems interesting off-hand, though I wonder if it's then worth punting this on the next SAA iterations and just declaring that we don't support these two worker types in the initial version.

johannhof commented 9 months ago

Another idea: We could solve the "existing worker" problem by also requiring dedicated workers to always call an rSA like API to get storage access, does that make sense?

annevk commented 9 months ago

Yeah, that could work.

wanderview commented 9 months ago

I haven't kept up with all the latest SAA changes, but my previous understanding was that the cookie grant was persistent. So that a reload of an iframe or the next load in a new tab will have cookies. Is that still the case? (If not, maybe ignore the following...)

If so, I think there might be some weirdness with service workers here. An iframe is intended to get the 1P cookies on reload, but if its intercepted by a service worker FetchEvent the SW will not get the 1P cookies. So a service worker doing evt.respondWith(fetch(evt.request)) will break.

Also, we had some discussion in privacycg about applying the cookie grant from SAA to bounce tracking mitigations checks. These checks are implicitly happening on navigations outside the context of where SAA was originally called. (There was some light head nodding that this was ok, but certainly not settled.)

I wonder if by trying to forcibly exclude workers we are in some way creating an inconsistency and extra complexity in relation to the cookie permission grants here.

annevk commented 9 months ago

It's not persistent. You'd have to call requestStorageAccess() again. However, I think you are correct that with explicit request cloning you might not get what you expect in service workers. (Requests would end up without cookies.)

We could perhaps forward some of the document's storage access state through the request and the request clone, but you'll end up with some amount of weirdness with multiple documents in different storage access states consuming the response. Seems unavoidable.

annevk commented 8 months ago

It would still be good to have test coverage and find out what implementations do today. What makes some sense to me:

jespertheend commented 8 months ago

It would still be good to have test coverage and find out what implementations do today.

These are not automated tests, but you can play around with cross site SharedWorkers on cors-communication-a.glitch.me and cors-communication-b.glitch.me. Both sites contain an iframe to a cross origin domain which can create a SharedWorker. The SharedWorker functions like a BroadcastChannel, sending any messages it receives to all connected ports.

From my testing, this is what browsers seem to do today:

Chrome

Version 120.0.6068.0 chrome://inspect/#workers shows two separate workers being created. Calling requestStorageAccess() doesn't have any effect.

Edge

Version 118.0.2088.46 SharedWorkers don't seem to get created at all. Instead, the moment an attempt is made to create a SharedWorker, an eye icon appears in the address bar which allows the user to enable third party cookies. Doing so reloads the page and (unpartitioned) SharedWorkers can now be created. requestStorageAccess() resolves or rejects based on whether the user currently has third party cookies enabled.

In version 120.0.2160.0 however, the behaviour is now on par with the other browsers. SharedWorkers are partitioned regardless of any requestStorageAccess() calls.

Safari

Technology Preview Release 180 (Safari 17.4, WebKit 19618.1.1.2) requestStorageAccess() is rejecting for me, I'm guessing it's because I haven't visited enough pages and Safari hasn't implemented any UI. Maybe someone who uses Safari more often than me can verify. But without storage access, it seems like Safari partitions SharedWorkers as well, judging from the received messages.

Firefox

Nightly 120.0a1 (2023-10-14) Firefox seems to also partition SharedWorkers, regardless of any requestStorageAccess() calls.

Opera

Version 103.0.4928.26 Opera does not seem to partition SharedWorkers.

annevk commented 8 months ago

@jespertheend this issue is not about whether they get partitioned or not. They should be partitioned. This is about what kind of cookies they get. (In Safari (and WebKit) you need a top-level visit to any cross-site site for requestStorageAccess() to succeed there in an embedded context.)

jespertheend commented 8 months ago

@annevk Ah I see, that makes sense now 😅 Is #102 the correct issue for this or should I create a new one?

edit: Seems requestStorageAccess() for non-cookie storage has an explainer here and is being prototyped in Chromium.

johannhof commented 7 months ago

It would still be good to have test coverage and find out what implementations do today. What makes some sense to me:

  • Dedicated workers inherit "storage access" upon creation. (Inherit in the sense that the state is copied. Later grants do not impact the worker.)
  • Shared workers and service workers do not inherit.
  • Service worker "pass through" requests pass along "storage access". So even if a service worker itself does not have storage access, if it's serving responses for a document that does those responses will still have relevant cookies most of the time. (But not when the service worker is making the requests itself.)
  • We create an API for all worker types that's equivalent to has/requestStorageAccess (or is maybe exactly that, except it would have to be on the global rather than a document).

This aligns with what I'd like to happen, @arichiv @dcthetall any concerns?

johannhof commented 7 months ago

Also @cfredric ^

bvandersloot-mozilla commented 7 months ago

I agree with what Anne laid out here, https://github.com/privacycg/storage-access/issues/157#issuecomment-1755290379, but am not too certain about the engineering complexity. I'll have to either talk to someone else about it or do some digging to be 100% certain :)

arichiv commented 7 months ago

@johannhof Re: https://github.com/privacycg/storage-access/issues/157#issuecomment-1755290379

  • Dedicated workers inherit "storage access" upon creation. (Inherit in the sense that the state is copied. Later grants do not impact the worker.)

Agreed, unless there's a compelling case for another approach this makes things simpler.

  • Shared workers and service workers do not inherit.

Agreed, this would be too hard to reason about as the worker pools would then be split.

  • Service worker "pass through" requests pass along "storage access". So even if a service worker itself does not have storage access, if it's serving responses for a document that does those responses will still have relevant cookies most of the time. (But not when the service worker is making the requests itself.)

Agreed

  • We create an API for all worker types that's equivalent to has/requestStorageAccess (or is maybe exactly that, except it would have to be on the global rather than a document).

Agreed, Chrome is discussing this now re: https://arichiv.github.io/saa-non-cookie-storage/. The debate is whether first-party Shared Workers should be accessible off of the requestStorageAccess handle returned to a third-party iframe and/or if third-party Shared Workers should just have a requestStorageAccess method of their own to use. The sticking point of that discussion centers on first-party Shared Workers having access to SameSite=Strict cookies.

smaug---- commented 7 months ago

Odd inconsistency if dedicated workers do inherit but shared workers for example don't. inconsistencies tend to bite us later.

bvandersloot-mozilla commented 7 months ago

If we push for consistency across workers, I think it makes more sense to have dedicated workers not inherit as well. Rather than having shared and service workers inherit.

johannhof commented 7 months ago

Hmm I like the inheritance for dedicated workers though, it kind of makes sense and web sites are already using this.

d3lm commented 4 months ago

Just to verify if I understood what's been discussed here about Service Workers, is the intention to not expose Service Worker after calling rSA? The use case that we have is that on a top level site like stackblitz.com we embed an iframe on a different domain webcontainer.io which then registers a SW. Since Chrome is phasing out third-party cookies it means that we wouldn't be able to register a SW anymore in the cross-site webcontainer.io frame.

There are ways to solve this like using a RWS (Related Website Set). However, a RWS becomes a bit tricky for our WebContainer API users because they would be embedding a cross-site frame to domains other than stackblitz.com, for example angular.dev. Phasing out third-party cookies also means breaking sites like the Angular docs. angular.dev could be added to the same RWS as stackblitz.com and webcontainer.io but this wouldn’t scale to all of our WebContainer API users. Alternatively, angular.dev could do their own RWS submission and use a separate preview site.

As a refresher, to submit a RWS such as angular.dev + preview site the preview site must satisfy the following:

Which basically means that for Angular they would need their own preview domain, e.g. angular-webcontainer-api.io. And we'd need that for every user that uses the WebContainer API and relies on previews.

Another option would be to have them setup a same site preview domain, like *.preview.angular.dev where that wildcard would point to our preview domain. But this won’t work for everyone and in some case you still have to rely on a RWS submission.

So I think being able to allow SW via rSA would be really great, or by any other means. What we are basically looking for is any way (could be a combination of headers, web API, user interaction etc…) to be allowed to install our SW.

arichiv commented 4 months ago

There are plans to add Shared Workers via rSA, could that help? https://privacycg.github.io/saa-non-cookie-storage/shared-workers.html

I don't think we have a clean way to resolve the history sniffing or cache poisoning concerns so the path for Service Workers via rSA is still blocked.

d3lm commented 4 months ago

Hm no, Shared Workers won't help in our case cause we rely on Service Workers for our networking stack within WebContainer.

wanderview commented 4 months ago

Since Chrome is phasing out third-party cookies it means that we wouldn't be able to register a SW anymore in the cross-site webcontainer.io frame.

Just to clarify, service workers are permitted in the cross-site iframe, but they will be in partitioned storage.

Nemikolh commented 4 months ago

@wanderview It hasn't been our experience nor the ones from the wordpress-playground folks (they have a detailed comment there: https://github.com/WordPress/wordpress-playground/issues/87#issuecomment-1601747967).

When third party cookies are blocked, service workers were also disabled. Or are you saying that this could be a bug in Chrome?

wanderview commented 4 months ago

We added this somewhat recently. I just tested quickly by loaded my blog in an iframe:

image image

Looks like this was fixed in M118:

https://chromiumdash.appspot.com/commit/e83ad48b7de16798e682a50ab68f5fcb3f09b3f9

d3lm commented 4 months ago

Thanks so much for the clarification @wanderview. Hearing this is a huge relief and we thought that we had to do a whole dance with RWS, domains, or proxies to get this to work. Big thanks to Steven I suppose for fixing this issue 🙏 🙏 🙏