Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.Sign up
Restricting cross-origin WindowProxy access (Cross-Origin-Opener-Policy) #3740
Add HTTP header called
When the HTTP response of a document has a
In a fully isolated document, access to any property on the window proxy of a cross-origin document (regardless of whether the target document is fully isolated or not) results in a
Furthermore, a new step is inserted into the concept of allowed to navigate before step 1: If B and/or A is isolated and A and B are not of the same origin, return false.
Let document A and document B be distinct documents its own browsing contexts. If A and B are of the same origin, the header has no effect. If A and B are cross-origin, then:
Spectre Protection Plan
For the purpose of protecting a website
If this header is set on
Let's say we're on some page B1 in
Let's say now
I believe that in order to make this header allow all toplevel isolated documents to be put in a distinct process, we need to be more restrictive as to what we let same-origin but isolated documents do.
Mini Window Agent Explainer
Before I started talking to @annevk about this stuff, I wasn't familiar with Window Agents, so I figured I'd include a small explainer:
Window Agents are the set of window globals which do, or may dynamically, have access to each-other's objects (other than the cross-origin
Two globals in the same Agent Cluster must be loaded in the same process, even with complete Site Isolation. They may share objects,
In this case, under the current proposal, we cannot actually put the isolated B-1 into a separate process. The reason for this is that A could embed a non-isolated iframe B-2, which would be in B-1's Window Agent according to the current logic.
This forces A to be same process with B-1, in case there exists a document B-2 which could be loaded, for example:
To fix this we need to prevent isolated and non-isolated documents from being in the same Window Agent, otherwise they can do the following:
// In B-2 let b1 = window.parent.opener; // b1 is same-origin-document with window, so we have to be same-process b1.document // :'-(
This brings us to the first restriction I think we have to make, namely:
Unfortunately, that restriction is not sufficient. If
For example, consider the case where a document B loads a frame C which, in turn, uses
In this situation, we cannot put A-1 in another process, as B could navigate the iframe C to be A-2, an isolated iframe which is
A-1 and A-2 must be in the same Window Agent, and thus the same process, as they are allowed to communicate, but we have no ability to move the iframe C out of process when loading A-2 in it, as we don't have OOP iframes.
This brings us to the second restriction I think we have to make, namely:
Shared Array Buffer
With those two restrictions, things are looking pretty good. Unfortunately,
Unfortunately this breaks our isolation story again, for example:
In this case, B-1 and B-2 are Same-Site, so we have 2 Window Actors:
Fortunately for us, A currently hides its opener from other documents due to its (mostly) opaque Cross-Origin
Notably, if the Toplevel Browsing Context's document is not isolated, we don't have opener hidden due to
In this case, we can reach from
This brings us to the last restriction I think we have to make, namely:
Restricted Window Agent Selection Process
All of that is a touch tricky to follow. This is the final, combined, Window Agent selection process:
To select a global
These are a few notable implications:
Allowed to Navigate
As navigating by named lookups can also create cross-global references (due to the opener property), we also have to restrict the allowed to navigate check, adding 2 new checks:
This should bring the checks in line with the Window Agent selection process.
This approach assumes that Window Agents are procedurally joined and used as the base of object access security. This is, however, not how the check is performed in the standard right now. We would want to change places which perform the Same Origin-Domain check for globals
This would change Window Agent selection to define access control, rather than being a result of it.
@mystor Thanks for the detailed feedback. All your points are valid and we made the same observation. However, what we concluded is that even those isolations are not enough because websites inside iframes would have access to the same set of cookies, local storage, etc... unless we treat it as a separate origin. If websites have access to those resources, we don't have a meaningful Spectre protection since the most sensitive information is stored in cookies and other storage APIs.
For this reason, we believe that in order for websites to protect themselves against Spectre in a browser which only supports top-level process isolation MUST deny themselves loaded in an untrusted cross-origin iframe at all, and all of their resources must have
More precisely, we believe websites MUST:
I'd also note that we've considered treating a website with
My worry here is that I don't think we can provide process isolation even if
Consider the example where we have a window
If I understand this proposal correctly, in this situation we definitely want
As we don't support OOP iframes, the theoretical
@mystor Thanks for the clarification. That's indeed an issue. There appears to be a number of possible solutions including but not limited to the one you proposed, checking all ancestor frames, clearing
Meanwhile, we've disabled this feature in WebKit since this discussion is likely to result in an incompatible behavior change to the HTTP header we're proposing.
Again, thanks for giving us the detailed feedback & engaging in the discussion with us.
@rniwa There's a typo, AFAICT in the first paragraph that tripped me up. You say "the value case-insensitively matches Deny ignoring cases" twice - I think the second one is supposed to be 'Allow-PostMessage', right?
And another typo below: "Then b.com doesn't have access to a.com, and b.com doesn't have access to a.com so we can put them into two different processes." - the second b.com/a.com should be swapped I believe.
Okay, here's our feedback.
Our new proposal is to only support
While it's regrettable that web pages that require
As I've previously stated, treating isolated and non-isolated pages as more or less different origins would make it harder to incrementally adopt this header across a website.
We've analyzed the proposal made by @mystor in detail but we could resolve two issues:
We've also considered keeping
Just wanting to make sure I have a clear idea of what your proposal here is, so I've done a slightly more technical writeup of what I am thinking you are suggesting. Please let me know if I am off-base.
I think I like this idea, it's fairly simple, but is unfortunately destructive (breaks all WindowProxy references in perpetuity), and doesn't support PostMessage (though I have a potential ugly workaround at the bottom).
While loading a Document
The following steps would be taken when loading a new document
This solution should be relatively straightforward to Firefox to implement, and it manages to avoid breaking all postMessage edges, but it probably can't be served on oauth popups.
I think I generally agree with this. I tried to think of how we could support postMessage within this framework, and came up with the following solution, which is unfortunately mildly compex/gross:
Effectively, this allows postMessage by keeping the method alive on closed WindowProxies created through the mechanism of this header, and using a dummy MessageEventSource method in the dispatched MessageEvent.
Are you describing a same origin document or a cross-origin document? In the case of a cross-origin document, the proposal calls for zero access. In the case of a same-origin document, yes, the proposal allows access, but no protection is needed.
Yes, to ensure process isolation you must specify
That said, the isolated document is still isolated in the sense that access to its window proxy properties is denied across origins.
A fresh auxiliary browsing context on the same origin will not be forced into a separate unit of related browsing contexts. But navigation to a different origin will trigger isolation. Isolation includes clearing the opener property. This allows a process swap and eliminates the need to specify
We don’t think this is mandated, but it is an option. We could go either way in the specification. Restoring the previous browsing context seems more web compatible.
We believe that
I had a chat about an alternative model with @mystor.
When creating an auxiliary/nested browsing context:
Per the last F2F discussion we had, we'd like to keep the level 1 proposal / feature to be focused on providing a mechanism for websites to protect themselves from Spectre attacks. That is, providing a way to enable SAB would be a good addition to this feature but shouldn't be a part / requirement of it. Also see Artur's summary in the isolation-policy mailing list.
Here's our latest proposal to that end.
We introduce a new HTTP header, let us call it
Conceptually, this is as if the user had closed the tab/window in which the navigation was happening, and opened a new tab/window and navigated to the same page.
There is no restriction on which website a document with the HTTP header can open in a new window or load in an iframe.
Various discussions we've had on this issue and elsewhere made us realize that the key issue with the process swap on navigation (PSON) in browsers that don't support frame-level process isolation is
To enable a website to protect itself, the user agent doesn't need to restrict the website’s ability to load cross-origin content in an iframe or a new window. In fact, that might be a necessary condition for some websites to adopt this new protection header. Imagine a banking website which opens another financial institution's website (e.g. credit card company's) in a new window in order to process a certain transaction. In such a case, it's probably okay for the websites to trust one another and be opened in a single process. In fact, such a flexibility might be a requirement for a website to incrementally adopt this new header.
Making those observations, the only restriction we need for websites to protect themselves from Spectre attacks in a browser which doesn't support frame-level process isolation is that another website can't open it in an auxiliary browsing context. Note the website MUST prevent others from loading inside an iframe of a cross-origin site from the supposition.
Furthermore, it seemed to us that the ability to protecting your own website from the opener browsing context from navigating, etc... seems like a useful feature regardless of Spectre.
A Path to Level Two Protection
To re-iterate our position during F2F, we think it's valuable to provide a mechanism whereby browsers without a frame-level process isolation can re-enable
One way to achieve the level 2 protection is to provide a way to prevent all descendent browsing context from loading a cross-origin content just like CSP's
When the browser sees that a website has this second level protection set at the top-level browsing context, then it can enable
One possible appraoch
We would have two values for the header:
Then the level two protection can be achieved by the combination of
Thanks for writing that up.
For "level 2" protection, is there any chance of including something like Mozilla's "X-Bikeshed-Allow-Unisolated-Embed" header (from the X-Bikeshed-Force-Isolate proposal) to allow web sites to opt in to being included in the same page and process as a site with SharedArrayBuffer access? Your description rules out all cross-origin browsing contexts, but that would (for example) prevent sites with SharedArrayBuffers from using ads, etc. I would imagine that ad sites might be ok with being embedded in such pages.
Having said that, how can something like
Again, our feedback / desire for the level two protection is that it ought to be something useful outside the context of Spectre and
Thanks for writing this up @rniwa!
I believe we will need to perform opener severing both for auxiliary and toplevel browsing contexts. If we only handle auxiliary browsing contexts, an attacker can open a new tab in the background, and then navigate itself to your toplevel page, attacking your document due to the
I think a way to make
Cases that I don't think we explicitly discussed:
I don't have strong feelings either way, so my guess is that we should aim for conceptually the simplest behavior. To me, it would make sense if the header was always ignored in a nested browsing context (so B would have the same behavior regardless of whether it sets the header). When B opens a pop-up, it seems reasonable for it to obey the unsafe-allow-outgoing value set on A -- this introduces a minor leak (A can know if B opened a new window), but this doesn't seem much worse than the status quo. Just my two cents, though...
One other thing that came up earlier but that AFAIK we haven't fully resolved is the
Okay, so tentatively for
Let's discuss CORS in #4175. Mozilla still intends to ship both together, but given the reservations stated in the meeting it might be good to somewhat separate the discussions for now.
Having written up a more formal description for COOP I realize I made some minor errors above. In particular there's no need for this policy to be on the browsing context group. Either auxiliary browsing contexts are allowed (if the unsafe flag is set) or they create new browsing context groups. So we only need changes to navigating a top-level browsing context (includes auxiliary, to be clear) and creating an auxiliary browsing context (might have to create a new browsing context group instead). Hope this new description aides in review.
I updated the description for COOP linked above with initial about:blank document handling and redirect handling. I think from a HTML Standard perspective we could also make "fast back" work by allowing browsing contexts to be closed/opened in addition to being discarded. We'd close it when replacing it with BCG and open it when going back. I'm not sure if this is worth specifying for v1 (although maybe it turns out to be easier), but as "fast back" is optional anyway I don't think it matters much.
The way the about:blank handling for unsafe-allow-outgoing should work out such that a navigation from initial about:blank ends up with replacement if there's a non-matching COOP on the response. I.e., if there's no COOP on the response, no replacement happens and you get an unsafe reference. If it's a "sameness" navigation and the response has a matching COOP, no replacement happens and you get a reference as well, because about:blank inherits COOP from its creator. A further navigation from that sameness response with COOP to a non-matching COOP would yield replacement. That seems like the right model since otherwise setting the header would sometimes not give the isolating effect it promises.
While working on #4284 I realized that non-auxiliary top-level browsing contexts can also get assigned external state:
(Apologies for originally posting these observations in the wrong issue. Thanks @csreis for the correction. I've marked the corresponding comments in the other issue as off-topic.)
Would the network error for sandbox documents only apply to top-level contexts, both non-auxiliary and auxiliary? If so, this is probably okay as they are indeed a fairly niche case -- otherwise, if we also network error'ed sandbox frames, it could be a larger obstacle to adoption, e.g. if the site has ads.
FWIW there are scenarios where maliciously putting a cross-origin document in the sandbox can be used in attacks (e.g. open a document from victim origin in a sandbox window which doesn't allow modals to prevent an alert() from warning the user about some unexpected condition). So the "easy option" of responding with a network error here might actually be security-positive.
Cross-Origin-Opener-Policy only takes effect in top-level, so yes. For nested contexts you could use X-Frame-Options. If you think further exploring dedicated anti-sandbox features are worth it, let's do that in a separate issue (it seems reasonable to me by the way, I was somewhat surprised we never really considered this before)?
Firefox's current approach is history is copied somewhat into the new browsing context group, so
What are Chrome and Safari considering here?
For Chrome, we're keeping the session history intact across browsing context groups, both for the back/forward buttons and history.back() et al. Chrome manages the session history for a tab in the browser (parent) process, with a notion of browsing context group (BrowsingInstance) and process (SiteInstance) associated with each session history item.
A risk with allowing access to history from JS is that upon a navigation from a site with COOP to an attacker's document the browser may leak interesting information (e.g. an attacker can get information about the number of navigations on the victim site via
If it adds significant implementation / spec difficulty then the severity of the leak is likely not worth addressing, but there are some benefits to removing JS access to history in this case.