Add HTTP header called Cross-Origin-Window-Policy, which takes a value of Deny, Allow, and Allow-PostMessage.
When the HTTP response of a document has a Cross-Origin-Window-Policy header, and the value case-insensitively matches Deny ignoring cases, the document is said to be fully isolated. If the value case-insensitively matches Allow-PostMessage ignoring cases, the document is said to be isolated with messaging. If the value doesn't match either or isn't set, then the document is said to be not isolated. If a document is fully isolated or isolated with messaging", it is said to be *isolated.
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 SecurityError. In a document isolated with messaging, access to any property except postMessage on the window proxy of a cross-origin document results in a SecurityError. The restriction between two documents are symmetrical and the most stricter of the two prevails.
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:
SecurityError. Any attempt to access a property on document A's window from document B also results in aSecurityError`.If document B is isolated with messaging and document A is not isolated. Any attempt to access a property except postMessage on document B's window from document A results in a SecurityError. Any attempt to access a property except postMessage on document A's window from document B results in a SecurityError.
If document B is isolated with messaging and document A is fully isolated. Any attempt to access a property on document B's window from document A results in a SecurityError. Any attempt to access a property on document A's window from document B results in a SecurityError.
If document B is isolated with messaging and document A is isolated with messaging. Any attempt to access a property except postMessage on document B's window from document A results in a SecurityError. Any attempt to access a property except postMessage on document A's window from document B results in a SecurityError.
For the purpose of protecting a website a.com from Spectre in browsers which support process swap for top-level navigations without frame-level process isolation, a.com can set this header on all of its documents (not setting on some would result in leaks; more on this later).
If this header is set on a.com, we can swap process on cross-origin navigation from or to a.com's documents because this header guarantees that a.com doesn't have access to any other document outside of its origin, and vice versa.
Let's say we're on some page B1 in b.com, and it window.open'ed (isolated) a.com. 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. Obviously, a.com's iframes don't have access to b.com's frame tree either so if a website is currently relying on being able to do this, they won't be able to use this header.
Let's say now a.com is navigated to some other page B2 in b.com. In this case, the browser finds the process which loaded B1 and load B2 in the same process so that they can talk to one another via window proxies.
/cc @whatwg/security. Very interesting...
Note that there are several different proposals in this space last I checked. @mystor was working on one too, somewhat different from the one above.
Note that this feature has been implemented in Safari 12: https://developer.apple.com/safari/whats-new/
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.
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 WindowProxy and Location objects). A _Window Agent_ is in an _Agent Cluster_ which also includes dedicated workers. Currently, two globals
have the same _Window Agent_ if they are loaded in _Related Browsing Contexts_ and are _Same Site_.
Two globals in the same _Agent Cluster_ must be loaded in the same process, even with complete Site Isolation. They may share objects, SharedArrayBuffer (even over BroadcastChannel), and share a single event loop.
+-----------+ +-----------+
| A | -opener-> | B-1 (I) |
+-----------+ +-----------+
(I): Isolated
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:
+-----------+ +-----------+
| A | -opener-> | B-1 (I) |
| | +-----------+
|+---------+|
|| B-2 ||
|+---------+|
+-----------+
(I): Isolated
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:
An _isolated_ global may only share its _Window Agent_ with another _isolated_ global
Unfortunately, that restriction is not sufficient. If window.opener references a _Nested Browsing Context_, we still run into problems with moving the document out of proces.
For example, consider the case where a document B loads a frame C which, in turn, uses window.open to open a new _Toplevel Browsing Context_ containing an isolated document A:
| B |
+-----------+ |+---------+|
| A-1 (I) | -opener-> | C ||
+-----------+ |+---------+|
+-----------+
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 Same Origin-Domain to A-1:
| B |
+-----------+ |+---------+|
| A-1 (I) | -opener-> | A-2 (I) ||
+-----------+ |+---------+|
+-----------+
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:
An _isolated_ global may only share its _Window Agent_ with another global with the same _Toplevel Browsing Context_
With those two restrictions, things are looking pretty good. Unfortunately, SharedArrayBuffer strikes again. SharedArrayBuffer can be sent over BroadcastChannel, and only requires two globals to share an _Agent Cluster_. Each of our _Window Agents_ has a corresponding _Agent Cluster_, which also contains off-main-thread dedicated workers.
Unfortunately this breaks our isolation story again, for example:
+-----------+ +-----------+
| A (I) | -opener-> | B-1 |
|+---------+| +-----------+
|| B-2 ||
|+---------+|
+-----------+
In this case, B-1 and B-2 are _Same-Site_, so we have 2 _Window Actors_: { A } and { B-1, B-2 }. This, again, forces us to load A in the same process as B-1 despite being isolated, in case it chooses to frame a document which shares a _Window Actor_ with its opener.
Fortunately for us, A currently hides its opener from other documents due to its (mostly) opaque Cross-Origin WindowProxy. Unless we split the _Window Actors_ though, these two documents may still BroadcastChannel each-other a SharedArrayBuffer object, so we need to be explicit about putting the documents in distinct _Window Actors_.
Notably, if the _Toplevel Browsing Context_'s document is not isolated, we don't have opener hidden due to window.top, for example:
+-----------+ +-----------+
| A | -opener-> | C-1 |
|+---------+| +-----------+
|| B (I) ||
||+-------+||
||| C-2 |||
||+-------+||
|+---------+|
+-----------+
In this case, we can reach from C-2 to C-1 using window.top.opener, so we should not put them in distinct _Window Agents_.
This brings us to the last restriction I think we have to make, namely:
A _non-isolated_ global may only share _Window Agent_ with another global with the same _Toplevel Browsing Context_, or, if both globals have a _non-isolated_ global in their _Toplevel Browsing Context_.
All of that is a touch tricky to follow. This is the final, combined, _Window Agent_ selection process:
To select a global A's _Window Agent_, consider each other global B, and join its _Window Agent_ if:
A and B are in _Related Browsing Contexts_, andA and B are _Same-Site_, andA and B are either both _isolated_ or both _non-isolated_, andA and B share a _Toplevel Browsing Context_, orA and B are _non-isolated_ and have _non-isolated_ globals in both of their _Toplevel Browsing Contexts_.These are a few notable implications:
postMessage.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:
A's or B's global is _isolated_, then:A's and B's global have different _Window Agents_, return falseA and B have different _Toplevel Browsing Contexts_:A's or B's _Toplevel Browsing Context_'s global is _isolated_, return falseThis 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 A and B to instead check:
A and B are _Same Origin-Domain_, andA and B are in the same _Window Agent_This would change _Window Agent_ selection to define access control, rather than being a result of it.
document.domainIn the past I have been under the impression that we are interested in also restricting the use of document.domain within isolated documents to make the _Window Agents_ even smaller. I worry that doing this restriction could prevent some websites from switching to isolated documents, but it may be desirable for security.
I'd be curious what people from Google and other websites think about this restriction.
@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 Cross-Origin-Window-Policy set to deny access.
More precisely, we believe websites MUST:
Cross-Origin-Resource-Policy header to same-site or same-origin (See https://github.com/whatwg/fetch/issues/687)Cross-Origin-Window-Policy header to Deny or Allow-PostMessage.X-Frame-Options header to DENY or SAMEORIGIN or CSP frame-ancestors.I'd also note that we've considered treating a website with Cross-Origin-Window-Policy set as a different origin but this posed a number of new challenges like having to update all our internal tracking of origins to keep an extra bit and passing it around in terms of implementation challenges, and it's confusing for developers that now URL alone can't determine the origin of a document, etc... so we decided against it.
My worry here is that I don't think we can provide process isolation _even if_
web developers opt into all of the headers.
Consider the example where we have a window A-1 which opens a window B-1.
B-1 follows all of the rules you've laid out above:
Cross-Origin-Resource-Policy: same-origin
Cross-Origin-Window-Policy: Deny
X-Frame-Options: DENY
+---------+ +---------+
| A-1 | <-opener- | B-1 (I) |
+---------+ +---------+
If I understand this proposal correctly, in this situation we definitely want
A-1 and B-1 to be in separate processes before we have OOP iframes. I don't
think we can do that, however.
For example, A-1 is capable of dynamically framing a new document, B-2 from
site B. As we do not know the set of documents and their headers from site
B, it is _theoretically_ possible for B-2 to frame a document setting none
of the above headers, even if a site doesn't serve any documents without them.
If A-1 held a cross-origin visible reference to B-1's browsing context,
(e.g. due to opener being reversed, which could be arranged without B-1's
cooperation through navigations, window.open, etc.) then the theoretical
iframe B-2 would be able to get synchronous access to B-1's JS global
through window.parent.opener, requiring them to be same-process.
Even if we could prevent A-1 from obtaining a cross-origin visible
reference to B-1's browsing context, B-2 would be in the same Agent
Cluster as B-1, which means that they could share SharedArrayBuffer
objects over a BroadcastChannel, requiring them to be same-process.
As we don't support OOP iframes, the theoretical B-2 must be in the same
process as A-1. However, B-2 must also be in the same process as B-1,
forcing B-1 and A-1 to be same-process.
@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 window.opener of any window opened by isolated documents, checking the access to top.opener based on whether it was opened by an isolated document, etc... We're having an internal discussion to either agree to the amendment you're proposing or further counter proposals.
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.
Just as an update, we're still discussing the best solution for this problem. Hoping to get back to you all within the next one month.
@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.
@tomrittervg Thanks for pointing that out. Fixed.
Okay, here's our feedback.
Our new proposal is to only support Allow and Deny, and remove Allow-PostMessage for now. When navigating in a window opened from or navigating to a fully isolated document (i.e. either the opener or the destination has Deny set) then, we would clear window.opener and create a new unit of related browsing context. This would mean that BroadcastChannel would not be delivered to those pages.
While it's regrettable that web pages that require postMessage won't be able to adopt this header in near future with this approach, we concluded this is the least problematic resolution of all.
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 Allow-PostMessage and only providing Spectre protection for Deny and not on Allow-PostMessage (i.e. browsers that don't implement frame-level process isolation would keep them in the same process) but we're worried that the message for developers will be too
confusing and misleading.
Our new proposal is to only support Allow and Deny, and remove Allow-PostMessage for now. When navigating in a window opened from or navigating to a fully isolated document (i.e. either the opener or the destination has Deny set) then, we would clear window.opener and create a new unit of related browsing context. This would mean that BroadcastChannel would not be delivered to those pages.
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).
The following steps would be taken when loading a new document doc into a browsing context context, with existing document oldDoc (the initial about:blank document is unisolated):
If neither of oldDoc and doc are isolated, skip the following steps
NOTE: This is the current case of
unisolated => unisolatedloads.
If oldDoc and doc are both isolated, and same-origin, skip the following steps
NOTE: Allow loads from
isolated(foo.com) => isolated(foo.com)to work as-today.
If context is a toplevel browsing context, perform the following steps
NOTE: This occurs in a few different cases:
isolated(foo.com) => isolated(bar.com)- we want to protect both, so make a new contextisolated(foo.com) => unisolated- protect the isolated document from unisolated document being loadedunisolated => isolated(foo.com)- protect the isolated document from previously loaded documents
newContext in a new window agentcontext into newContextcontext into newContextdoc into newContextcontext with newContext in the browser uicontextX-Frame-Options to protect themselvesnoopener to protect themselvespostMessage edges are not supported.about:blank document, doc, could be treated more specially. Specifically:doc has an isolated original opener document opener, and the to-be-loaded document is isolated, doc is the same as openerdoc is unisolatedWhile it's regrettable that web pages that require postMessage won't be able to adopt this header in near future with this approach, we concluded this is the least problematic resolution of all.
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.
We've also considered keeping Allow-PostMessage and only providing Spectre protection for Deny and not on Allow-PostMessage (i.e. browsers that don't implement frame-level process isolation would keep them in the same process) but we're worried that the message for developers will be too
confusing and misleading.
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:
Allow-PostMessage is set, all WindowProxy references to the original browsing context, context, have the internal [forwardPostMessage] property set to newContextWindow::postMessage method where this is a WindowProxy to a closed window, check [forwardPostMessage]. If it is set, perform the following steps:source be the calling contexttarget be the context stored in [forwardPostMessage]message using the structured clone algorithm.target's origin does not match targetOrigin, abort these stepsevent with the data being the structured clone of message, origin being the origin of source, and source being a MessageEventSource object with a postMessage method, which can be called to perform these steps, but in the other directionevent on the current global of targetEffectively, 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.
Non-Protections
- The isolated document may frame any document, and has full normal access to that document. These subframes would be able to attack the isolated document
Sites should be careful with which documents they frame, potentially using CSP for added control
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.
- If the isolated document is framed by an attacker, it will not be isolated
Sites must useX-Frame-Optionsto protect themselves
Yes, to ensure process isolation you must specify X-Frame-Options or CSP's frame-ancestors.
That said, the isolated document is still isolated in the sense that access to its window proxy properties is denied across origins.
- Auxiliary browsing contexts created by the isolated document or its subframes are not forced into a separate process. Sites must create auxiliary browsing contexts with
noopenerto protect themselves.
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 noopener separately.
- Documents with this header may be loaded by cors-exempt subresource loads
This will be mitigated by CORB and other strategies
Right.
Side-Effects
- Navigating to previously loaded pages will load them in a newly created browsing context, rather than the context they were originally loaded in.
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.
- Existing postMessage edges are not supported.
Right.
Potential Variations.
- The initial
about:blankdocument, doc, could be treated more specially. Specifically: This would allow isolated documents to open same-origin isolated auxiliary browsing contexts without breaking window references, while also continuing to allow unisolated auxiliary browsing contexts etc. However, this could make for a larger footgun compared to the above model, and is more complex.
We believe that about:blank has the origin of its opener. So we don’t understand the issue here. Maybe we're using the wrong terminology here?
I had a chat about an alternative model with @mystor.
Ingredients:
When creating an auxiliary/nested browsing context:
When navigating:
If one of the following is true
and the navigation crosses the isolate's origin coupled with same-origin/same-site boundary, then create a new top-level browsing context to handle the navigation and close the currently navigated browsing context. (This severs all connections.)
Tradeoffs:
Goals:
UI:
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 Cross-Origin-Opener-Policy for the sake of this description. The name can be changed to whatever we like. When navigating to a document with this header set in an auxiliary browsing context or any other top-level browsing context, the auxiliary browsing context is closed. The navigation instead occurs in a new browsing context created with its own unit of related browsing context.
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 window.opener. Specifically, a nested browsing context (iframe) inside a cross-origin auxiliary browsing context can access the isolated document via top.opener. So we must sever this connection.
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.
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 SharedArrayBuffer provided the same feature is independently useful in some other context.
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 frame-ancestors allows websites to prevent cross-origin websites to load itself.
When the browser sees that a website has this second level protection set at the top-level browsing context, then it can enable SharedArrayBuffer because such a browsing context cannot have, in its unit of related browsing context, any browsing context of cross-origin.
We would have two values for the header: deny-incoming and deny-all. deny-incoming is level one protection: your opener gets severed, but you can open whatever you want. deny-all is level two protection: you get deny-incoming and you also sever cross-origin windows you open. (As with the rest of the proposal, the names can change once we agree on the behavior.)
Then the level two protection can be achieved by the combination of deny-all and csp: frame-src self at the top-level browsing context.
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.
@csreis : Perhaps. The level two protection proposal outlined in my latest comment is simply a possibility / an option. We're not necessarily fixated on it.
Having said that, how can something like X-Bikeshed-Allow-Unisolated-Embed be useful outside the very limited context of allowing SharedArrayBuffer to exist in a website which loads cross-origin ads in a browser which doesn't support frame-level process isolation?
Again, our feedback / desire for the level two protection is that it ought to be something useful outside the context of Spectre and SharedArrayBuffer.
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 opener property on the background tab.
I think a way to make X-Bikeshed-Allow-Unisolated-Embed useful beyond spectre would be for it to become part of a system of mutual consent. Namely, a level-2 isolated document both needs to consent to loading a nested frame, and the nested frame needs to consent to being loaded. This might be nice for websites wanting to protect themselves through better hygiene in general.
One more thing - I don't believe we can re-use frame-src 'self' as it isn't inherited at the moment by subframes. I've thrown together a quick little demo page: https://yielding-bobolink.glitch.me/

@rniwa: Ah, that's a different question if browsers with frame-level process isolation are allowed to load cross-origin browsing contexts in your level 2 proposal. That should be safe from the Spectre perspective, but it's functionally different than browsers without frame-level process isolation would behave.
On the plus side, it avoids the need for another header. However, it means that ads (etc) wouldn't be supported on sites with SharedArrayBuffers in most browsers until/unless they add frame-level process isolation. That might be a tough story for sites deciding whether to use level 2 / SharedArrayBuffers.
Of course, if all browsers do add frame-level process isolation, I'm not sure we gain much from requiring mutual consent in the long run (and why it would be specific to any features gated on level 2). @mystor, are there certain things you're thinking that would provide protection from?
Alternatively, would it be worth considering X-Bikeshed-Allow-Unisolated-Embed as a temporary header, as I think it was described in the Bikeshed proposal doc?
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 opener property on the background tab.
That's a good point. We probably need to do that. I've updated the proposal.
One more thing - I don't believe we can re-use frame-src 'self' as it isn't inherited at the moment by subframes. I've thrown together a quick little demo page: https://yielding-bobolink.glitch.me/
Oh, right, we probably need a new CSP directive, or make level-two protection of this header to trigger that behavior. Note that this behavior isn't needed for level-one protection since our proposal is that we would allow websites to load and open cross-origin content as they wish.
@rniwa: While we're on the topic, the new header in your level 1 proposal would still apply to browsers with frame-level process isolation, correct? That is, we would still sever the connection to other windows when navigating to a document with the header.
This wouldn't be necessary for Spectre or for correctness (since it's possible for page A(B) to open B in a new window and have the two B documents in the same process as each other), but I agree that it's still useful from a web security standpoint. The top-level B might reasonably want to make sure no existing window has a reference to it.
+1 to also protecting the window from auxiliary browsing contexts that it itself opens as discussed above -- my guess is that the model of "only my own frames or their descendants can get a direct reference to me" is both powerful security-wise and simple to understand/adopt. AIUI it would also be sufficient for Spectre mitigations in browsers without frame-level process isolation.
@csreis / @mystor Would it make sense to try and solve the framing problem you're talking about with CORS? It will likely already be necessary to force pages which are put into "level 2" isolation to load their subresources via CORS (to protect against no-cors-mode loads of non-cooperating resources exempted from CORB, such as images). If we could apply the same requirement to iframes embedded in the isolated document it would prevent the document from iframing non-cooperating sites, and might potentially eliminate the need for a special header (or if we want explicit consent from the embeddee we could still do it via an Access-Control-Allow-* header and reuse CORS infrastructure).
There are two caveats here:
no-cors mode into the attacker's address space. If we did it this way, then I think "Level 2" would boil down to enabling the restrictions of "Level 1", requiring CORS for all fetches initiated by the document, and recursively propagating to the document's iframes (the concept of which already exists in the HTML sandbox and Feature Policy, as mentioned above). I think this would be in line with @rniwa's comment about making the mode more general than as just a Spectre protection (e.g. a similar design was proposed to address some security concerns with the performance.memory API).
@rniwa: While we're on the topic, the new header in your level 1 proposal would still apply to browsers with frame-level process isolation, correct? That is, we would still sever the connection to other windows when navigating to a document with the header.
Yes! The point of our proposal is to allow Spectre protection in browsers without a frame-level process isolation but making it a useful security feature independent of that.
@arturjanc : Yeah, something along that line of the thought would probably work, and it would indeed be useful post Spectre.
Somewhat orthogonally, I wonder how important of a use case is to use SharedArrayBuffer and third party ads in a single page. And presumably, we wouldn't let those ads use SharedArrayBuffer because that would allow ads to attack the embedding document? It would be great if someone could clarify that.
By the way, is everyone happy with what we're proposing for level one protection? We like that it's a very simple & easy feature for web developers to understand (e.g. severs window.opener and the window handle in opener's browsing context, and it doesn't seem to preclude any of the level two protections we've discussed so far.
It's not very clear to me how your proposal relates to origins (and whether same-site is being considered).
@arturjanc overloading CORS seems problematic:
(I'm a bit surprised nobody gave concrete feedback to what @mystor and I wrote up.)
@csreis
Alternatively, would it be worth considering X-Bikeshed-Allow-Unisolated-Embed as a temporary header, as I think it was described in the Bikeshed proposal doc?
Unfortunately I that header only really makes sense if we are using a temporary header to enable level-2 protections as well. If we are using something like frame-src: 'self' as the hint, the header cannot allow you to bypass that restriction.
@csreis
While we're on the topic, the new header in your level 1 proposal would still apply to browsers with frame-level process isolation, correct? That is, we would still sever the connection to other windows when navigating to a document with the header.
Yes, the plan is to have none of the standardized headers have different effects depending on whether frame-level site isolation is avaliable. The goal is to make generally useful headers.
The only major difference right now would be that SAB/WASM threads would only be available in documents which have L2 in non-frame-level-isolated browsers, however that wouldn't be part of the standard.
@arturjanc
If we did it this way, then I think "Level 2" would boil down to enabling the restrictions of "Level 1", requiring CORS for all fetches initiated by the document, and recursively propagating to the document's iframes (the concept of which already exists in the HTML sandbox and Feature Policy, as mentioned above). I think this would be in line with @rniwa's comment about making the mode more general than as just a Spectre protection (e.g. a similar design was proposed to address some security concerns with the performance.memory API).
My main worry here is the same as @annevk's concern, which is that CORS is fairly deeply tied into subresources, and integrating it with navigation could be complicated & error prone. If we decide that this is the approach we want to take we could potentially look into it.
@rniwa
Somewhat orthogonally, I wonder how important of a use case is to use
SharedArrayBufferand third party ads in a single page. And presumably, we wouldn't let those ads useSharedArrayBufferbecause that would allow ads to attack the embedding document? It would be great if someone could clarify that.
The primary example I can see here would be if Facebook or similar wanted to set this header in their toplevel embedding page to load a game with access to SharedArrayBuffer from a cross-origin resource. This situation may be something which is too unsafe, and we don't want to be supporting.
@rniwa
By the way, is everyone happy with what we're proposing for level one protection? We like that it's a very simple & easy feature for web developers to understand (e.g. severs
window.openerand the window handle in opener's browsing context, and it doesn't seem to preclude any of the level two protections we've discussed so far.
I'm generaly comfortable with it, it appears as though it's a fairly straightforward approach to take.
In effect it does:
noopener for links.The main disadvantage of this proposal is that L1-protected documents have to be very careful when navigating. Specifically, if they open any auxiliary browsing contexts, they cannot navigate to an untrusted cross-origin resource, as the load will be unable to leave the current process.
It may be advantageous to also do a fresh load when navigating away from an L1 isolated document (L2 isolated documents cannot have auxiliary browsing contexts, so it is not an issue for them).
This proposal in general seems like a simpler but more strict version of the proposal made by myself and @annevk above (e.g. it prohibits L2 documents from having any auxiliary browsing contexts), however that may be desirable.
Some more comments:
When navigating to a document with this header set in an auxiliary browsing context or any other top-level browsing context, the auxiliary browsing context is closed. The navigation instead occurs in a new browsing context created with its own unit of related browsing context.
We should also note that some state needs to be copied between the old and new browsing contexts (specifically session history). We should also note the interactions with BFCache (can the previous document remain suspended? What happens when you go back? Do we un-close browsing contexts when navigating through history?).
In general I think clarity should be added to what happens around session history in the proposal.
@annevk
It's not very clear to me how your proposal relates to origins (and whether same-site is being considered).
I think @rniwa's proposal avoids dealing with the site/origin problem by simply always cutting existing (deny-incoming), or existing and new (deny-all) links. In effect, if you are a root document with this header, you are guaranteed to be in a toplevel browsing context and have no auxiliary browsing contexts.
The problem of same-site vs same would then come down to whatever proposal is used for controlling what documents you're allowed to frame, which appears to still be fairly contended.
The main disadvantage of this proposal is that L1-protected documents have to be very careful when navigating. Specifically, if they open any auxiliary browsing contexts, they cannot navigate to an untrusted cross-origin resource, as the load will be unable to leave the current process.
It may be advantageous to also do a fresh load when navigating away from an L1 isolated document (L2 isolated documents cannot have auxiliary browsing contexts, so it is not an issue for them).
Wouldn't that break OAuth workflow? One aspect of what we like about our current proposal is that the existing OAuth workflow would continue to work assuming both OAuth provider & OAuth user trust one another.
Note that with our proposal it would also work, as long as the OAuth flow happens in an <iframe> or auxiliary browsing context. If I understand things correctly with your proposal a stray <a href=[notfullytrustedcrossorigin]> could end up being problematic.
If I understand things correctly with your proposal a stray
<a href=[notfullytrustedcrossorigin]>could end up being problematic.
Could you clarify what the problematic behavior is?
notfullytrustedcrossorigin wouldn't end up process-isolated from the current document.
Oh, yes, a navigation away from an isolated document / browsing context should also sever the opener of the browsing context in https://github.com/whatwg/html/issues/3740#issuecomment-426449368
We like not severing the opener when navigating in an auxiliary browsing context opened by an isolated document in order to keep OAuth working. Namely, if an isolated site a.com in a browsing context A opens non-isolated a.com in an auxiliary browsing context B, and B navigates to OAuth provider b.com, we want b.com in B to be able to communicate with a.com in A. i.e. they should stay in the same process. This seems an okay from the threat model of the website since a.com trusts b.com in this case.
If a.com in A were to open an auxiliary browsing context C which can navigate to any content, e.g. third party news site, then a.com can open the auxiliary browsing context using one of its isolated pages of a.com. Then navigating to any cross-origin page would sever the opener relationship, and would allow UA to do a process swap.
This approach seems to strike a good balance between the isolation we want to provide in order to allow process swaps while retaining the ability for a website to communicate directly with trusted websites.
So the difference between our proposals is that with your proposal even a same-origin/site navigation would end up in a new browsing context (process).
@annevk : Oops, sorry, we should clarify that the severing happens only across cross-site navigations. I think the key difference is whether top-level browsing context's isolated status inherits to auxiliary browsing context or not.
We think it's better not to do that because that would websites to have a control over what happens (for an easier adaption of the feature), and avoid special-casing top-level browsing context.
No, not even that is different then. (I'm again somewhat dismayed nobody gave feedback on ours, was it that unclear?) That only happens in our proposal for strict (l2) isolation, in that case the auxiliary browsing context would end up being replaced with a new top-level browsing context, otherwise it doesn't really take effect and you wouldn't have to copy the state (it's copied for simplicity since it's needed for strict). We thought that would be more useful than not allowing them to be created at all.
Hm... we must have misunderstood your proposal then. Sorry, we were focused on describing what we agreed during F2F meeting around the time you made your proposal, and we sort of got carried away.
But it's a good news that it seems like we're mostly on the agreement in terms of what we want for level one protection!
By the way, are people coming to TPAC? Can we have a breakout session for this topic on Wed?
I won't be at TPAC, but hopefully @arturjanc will? However, I've been spending most of today trying to understand both the Mozilla and Apple proposals in more depth to figure out my questions about them. I'll try to boil it down and share something before TPAC.
Ok, I wrote up my understanding of the Mozilla and Apple proposals, from the perspective of how they relate to Spectre mitigations (Level 1) and enabling SharedArrayBuffers (Level 2), as well as what might be expected of browsers with frame-level isolation (e.g., Chrome). It's a bit long to put into a comment here, so I put in this document:
https://docs.google.com/document/d/19eGHUnLaARKpE4PofTNDil5Ix9I-AGYQEB-Rr2V9sfg/edit?usp=sharing
The document is open for comments if you want to reply to anything there, or we can discuss it here if you'd prefer. There's a few things I'm not clear on in the proposals (which I've called out in red in the document), but on the whole I agree that we seem to be getting close.
Hope the document is helpful as a checkpoint for where we are (given the long discussion here), and that I'm not too far off in my understanding!
@csreis thank you so much, great resource! I left a bunch of comments. Glad to see we're mostly on the same page.
Thanks for the comments! You gave some useful clarifications for things I missed. I have a few questions remaining, but it's coming into focus a bit more now. @rniwa, @mystor, and @arturjanc, feel free to take a look as well if you get a chance.
Thanks, @csreis! I really like your doc and left some minor comments -- my only request would be to, at some point, translate the proposals into a bit more web-friendly terms so that developers who consider adopting them would understand the consequences for their applications (e.g. the severing of opener relationships) and could provide feedback.
I will be at TPAC and am setting aside time on Wednesday to talk about this (hopefully we can figure out the scheduling during the WebAppSec session).
Thanks. Agreed that we should be getting feedback from developers who might adopt these.
On that note, I was discussing this more with our team today, and it stood out that OAuth providers would not be able to adopt L1 or L2 headers on their OAuth flows with either of these proposals. That actually seems like quite a big problem, since they have a strong incentive for L1, given that users will be entering passwords and getting login tokens in their process. If they did use L1 headers, they wouldn't be able to communicate back to the original site that the login succeeded.
Given that OAuth providers are pretty important to protect, are there any things we can recommend for them? Should we be advocating a different OAuth flow that involves navigating from foo.com to auth.com and back in the same window (with fresh loads each time), passing the token via the URL? (And if they have to do that anyway, does that reduce the need for us to keep cross-origin popups in an L1 document's process?)
In general, this reminds me of the issues covered in my earlier writeup from May, though we didn't seem to like the "credential-less subframe" possibility mentioned there.
F2F notes thanks to @arturjanc. Unfortunately @csreis's feedback did not get discussed. A summary of the headers we did discuss:
Cross-Origin-Opener-Policy = sameness [ RWS outgoing ]
Upgrade-No-Cors = sameness
Cross-Origin-Frame-Policy = sameness
sameness = %s"same-origin" / %s"same-site" ; case-sensitive
outgoing = %s"unsafe-allow-outgoing" ; case-sensitive
It seems if we decide that OAuth needs to be L1-protected we can remove the [ RWS outgoing ] bit. (So every window opened is as if noopener was specified.)
A special thing with Upgrade-No-CORS not discussed at the meeting is that it would have to inherit into dedicated workers as well (it already inherits into sameness nested browsing contexts). Other workers are never in the same agent cluster so they need to have it set on them specifically (also, inheritance would be racy). (This header is also independently useful for things like the memory API. Perhaps we can also bind navigator.storage.estimate() to it...)
Cross-Opener-Policy is probably the hardest to define given the generally unsound infrastructure, but probably still doable. I don't know if anyone was planning on working on this, but I'd be happy to help out.
Feedback on this from @whatwg/security is welcome.
In terms of OAuth providers being L1 or L2 protected, I think they (technically) could do it, but it would be messy... OAuth providers need the opener relationship to communicate info back to the requestor, and if the provider is L1 isolated it won't have any opener relationship to the requestor.
Off of the top of my head, I think the OAuth provider could work around this by:
As to what would be in that reply mesasge, it might have to look something like:
Alternatively, the communication could be entirely handled by the provider's server, using WebSockets or similar.
Unfortunately, I think this flow is super error-prone and easy to get wrong, so I imagine that it won't get much adoption.
If we want to preserve the opener relationship from a potentially-malicious page to the OAuth UI page, we would have to change strategy. The recent discussions have all been centered around performing isolation in a simple yet permissive manner: namely by removing existing opener relationships which would force the page to be loaded in-process. This is pretty fundamentally at-odds with the OAuth UI use-case.
If I remember correctly, the reason we chose to go this way is that it:
The other options which we discussed earlier centred around changing the WindowProxy behaviour. These proposals ended up failing because, in order to achieve isolation, we need to change the behaviour of WindowProxy objects for same origin-domain documents. Otherwise, an attacker document _could_ frame a same origin-domain document which would need sync access to our isolated document.
If we were comfortable changing the behaviour of WindowProxy objects for same origin-domain documents, we could do something like:
isolated flag on the [Environment Settings Object] for its associated realm. Nested browsing contexts inherit this isolated flag from their parent, and set it on all created ESOs.isolated is set.In effect, this would make WindowProxy objects of isolated documents act as though they are dissimilar-origin for the purposes of script access, meaning that we can put tabs with this header in new processes, as they, and their subframes, are dissimilar-origin from all other documents. Unfortunately this would prevent same-origin access to subframes. This could be worked around by also comparing the toplevel browsing contexts of the current and relevant globals.
@mystor: Thanks. I agree that there are downsides to treating same-origin frames as if they were cross-origin, since that can get confusing. It may be possible to go down that path if needed, but you also point out some ways OAuth providers might be able communicate across units of related browsing contexts (though they may have their own downsides). And it does seem appealing to have a header that is useful independent of Spectre protections, so I agree there's some appeal to the approach of severing window references.
Maybe we should seek feedback from potential adopters of these headers (including OAuth providers) to confirm we're building something useful for them?
@annevk: Thanks for the summary. Can you elaborate on what Upgrade-No-Cors and Cross-Origin-Frame-Policy do? The writeup is a little short on details.
I'm also curious if the L2 part needs to continue blocking cross-origin iframes if browsers gain support for frame-level process isolation. I'm not sure what the long-term benefits are in that case (the way that severing window references is useful outside Spectre). Loading those iframes in a separate process seems safe even if the parent document has access to SharedArrayBuffers and other precise timers.
@csreis in browsers without cross-process frames you need something like Cross-Origin-Frame-Policy to restrict what descendant frames can navigate to. Navigation across the "sameness" boundary would result in a network error. This header has somewhat less utility with cross-process frames.
Upgrade-No-CORS inherits into any nested or popup frame, or dedicated worker. It sets a flag checked by Fetch that will change all "no-cors" fetches to "cors" fetches. This way whoever uses the header cannot perform attacks on "no-cors" resources (similar to CORB, but for all resources except navigations).
The combination of these headers (and all having equal "sameness" values) would enable high resolution timers.
@annevk: Thanks for elaborating! And will it be permitted to ignore Cross-Origin-Frame-Policy if the browser can put such frames into a different process? Just want to make sure the spec isn't requiring that the navigation fails if it can be done safely.
@csreis I'm not sure if that makes sense, since you really need both that and Upgrade-No-CORS. Enforcing Upgrade-No-CORS on cross-origin documents seems problematic in a way.
I agree with Upgrade-No-CORS being problematic for cross-origin iframes-- that could possibly be ignored in cross-process subframes as well, or we could look for a different way to approach this restriction.
Mainly, I think we need a way to avoid unnecessary long-term restrictions on pages that request high resolution timers. I can see how Upgrade-No-CORS is useful for those documents and any descendants in the same process (even in the long run), but it stops being necessary for cross-origin subframes if/when browsers can load them in a different process.
Can we design this in a way that allows for it to gracefully upgrade, allowing cross-origin subframes to load in a different process without the CORS restriction?
Yeah sorry, my response needed more nuance, Upgrade-No-CORS would only need to "inherit" within the agent cluster (which by definition is same-site restricted). The main subtlety where you'd need both I think is if you want same-origin and not same-site. Since cross-origin, but same-site, will be in the same agent cluster and thus vulnerable in a way. (We could also chose not to offer same-origin protection for now I suppose.)
Sorry for the delayed reply. I'm not sure if that answered my main question, which is whether we will allow cross-origin iframes in pages that want precise time, if the browser can support cross-process frames.
Two possible ways we might do that:
1) Cross-Origin-Frame-Policy always prevents cross-origin iframes (and thus the Upgrade-No-CORS issue is moot). However, browsers with cross-process frame support can choose to grant access to SharedArrayBuffers, etc, without requiring Cross-Origin-Frame-Policy. Sites can choose whether to include the Cross-Origin-Frame-Policy header based on User Agent. (This is arguably not great, because it requires sites to know which browsers will give them SharedArrayBuffers with and without Cross-Origin-Frame-Policy.)
2) Cross-Origin-Frame-Policy itself can be ignored by browsers with cross-process frames, and Upgrade-No-CORS only applies within the agent cluster. Sites can then serve the Cross-Origin-Frame-Policy header and put cross-origin frames in their page, with the understanding that browsers will display those frames only if it can be done safely (i.e., in a different process). This means the Cross-Origin-Frame-Policy header may lose its meaning over time if more browsers get support for cross-process frames, but so far I haven't heard a strong reason to keep it after cross-process frames are possible.
Would we be ok with the latter?
I don't think (2) makes sense. Preventing descendent frames in your page to navigate to an URL outside of your own origin/site is a useful feature on its own. What we can do is that once all major browsers have supported per-frame process isolation, then we can enable high precision timers & shared array buffers without these headers.
I don't think we can simultaneously allow some UAs to start exposing high precision timers, and at the same time no cross-UA behavior difference since those two things are fundamentally at odds. Fundamentally, UAs that support per-frame process isolation would allow high precision timers in contexts where other UAs won't, or all UAs are stuck with not enabling high precision timers until all UAs support per-frame process isolation if we wanted the interoperable behaviors across UAs.
Either way, I don't think some UAs ignoring Cross-Origin-Frame-Policy would make much sense because that's no worse than (2) in terms of interoperability and eliminates the benefit of this header.
Note that I think we should always require Upgrade-No-CORS for high resolution timers of any kind (including SharedArrayBuffer). Otherwise attacking resources (that fall outside the CORB safelist) with a high-bandwidth channel is just too easy.
Given what @annevk said above, I think we have rough agreement about the behavior we want from the L1 and L2 mechanisms, so perhaps it's a good time to have a pie fight about the spelling? ;-) Here are some comments, using https://github.com/whatwg/html/issues/3740#issuecomment-433945551 as the baseline proposal.
My main thought is that for L2 it might make sense to combine Upgrade-No-Cors and Cross-Origin-Frame-Policy into a single header. Given that in https://github.com/whatwg/html/issues/4175 we're considering CORS as the mechanism that would let servers consent to iframing by an L2-isolated document, both of the headers boil down to requiring CORS for cross-origin loads (subresources and frames, respectively). Given that we need both restrictions to enable high-res timers, it might be confusing to expose two separate switches where one suffices:
Require-CORS: true which enforces the use of CORS for non-same-origin subresources and frame loads and propagates through descendant iframes to be a fairly straightforward switch that would be sufficient for most applications.For the Cross-Origin-Opener-Policy proposal, my main comment is along the lines of (2) above -- _sameness_ seems a little more useful in this context, but I'm still not sure if this is a switch that developers really need. Similar to Ryosuke's proposal above and Nika's comment, I'd find COOP: deny-incoming | deny-outgoing | deny-all to be fairly straightforward (though I agree with Anne that it could be a blocker in some scenarios; my hope is that it's more of a niche use case, but I could certainly be convinced otherwise.)
In this world:
COOP: deny-incoming + CORP/SameSite cookies/Sec-Metadata to protect any server responses that aren't covered by CORB.COOP: deny-allCOOP: deny-all and Require-CORS: true and would give access to SharedArrayBuffer and potentially other dangerous features.Would something like this make sense? (Apologies in advance if I omitted any important considerations from the discussions above.)
I prefer the allow-outgoing design in that you have to be explicit in what to allow. It's also not clear to me that deny-outgoing on its own is something we want or discussed.
It also seems that if we can do CORS preflights for navigations, we can do those for popup navigations too and should perhaps allow allow-outgoing in L2, provided that mechanism is in use.
Today's Hangout summary:
https://github.com/whatwg/html/issues/3740#issuecomment-433945551 is the current consensus with a clarification that navigation to and from a document with Cross-Origin-Opener-Policy would always result in a new browsing context group. allow-outgoing would only apply to navigations within auxiliary browsing contexts it opened. We'd leave an informal note saying that the author should be deploying CSP navigate-to directive when using allow-outgoing.
We also agreed on renaming allow-outgoing to unsafe-allow-outgoing to signify the risk of letting others have a reference to you (and be in your process in some implementations).
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 Upgrade-No-CORS header and whether to automatically supply credentials on upgraded requests. I think we'd need to do it to not require significant refactoring of code/markup which loads authenticated subresources; but this behavior could be a little surprising, so we should probably put some thought into this.
Okay, so tentatively for Cross-Origin-Opener-Policy:
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.
(I didn't realize this originally covered CORS as well, my bad. I think it would make sense to keep this for Cross-Origin-Opener-Policy and the other issue for enabling SharedArrayBuffer.)
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:
Cross-Origin-Opener-Policy is specified we should get rid of the name. This has no negative consequences that I can think of. The site itself can still set it.Cross-Origin-Opener-Policy set we run into this. If A used noopener we also run into this and in that case we copy over the flags. So we'll have to support creating a new browsing context group with some inherited flags, but it would be nice if Cross-Origin-Opener-Policy meant no inherited state, so I'd propose we network error here given that it's a nice cache.(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)?
A thing we have not discussed in detail here, though @mystor did mention it in https://github.com/whatwg/html/issues/3740#issuecomment-399194225 is history.
Firefox's current approach is history is copied somewhat into the new browsing context group, so history.back() et al do function to some extent. My personal feeling is that the UX for history should continue to work, but API-wise it should feel as if the user navigated directly to your (COOP) document.
What are Chrome and Safari considering here?
A thing we have not discussed in detail here, though @mystor did mention it in #3740 (comment) is history.
Firefox's current approach is history is copied somewhat into the new browsing context group, so
history.back()et al do function to some extent. My personal feeling is that the UX for history should continue to work, but API-wise it should feel as if the user navigated directly to your (COOP) document.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.
I think the desired behavior here is what @annevk described in https://github.com/whatwg/html/issues/3740#issuecomment-486170969: we want history navigations via the browser UI to work, but ideally they wouldn't be accessible via window.history.
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 window.history.length).
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.
As a result of some related issues, in particular how inheritance ought to work and whether unsafe-inherit would be sufficient if an OAuth-chain had cross-site documents in it as well, we ended up simplifying Cross-Origin-Opener-Policy a bit. It now has an explicit default, unsafe-none, which is mostly there to potentially allow for changing the default at a future point (and allow sites to opt out of such a change). And the remaining values are same-origin and same-origin-allow-popups. Given the problems with unsafe-inherit there seemed to be less of a need for same-site-like policies, but adding those back in would not be a lot of work if developers request them.
https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e is still the canonical document and I've updated it with these changes.
The Cross-Origin-Opener-Policy header seems to be quite similar to what the rel="noopener noreferrer" attribute does when opening a new document in a new tab (target="_blank").
When should I use which one? It seems the COOP header is applicable when I link between origins while the rel="noopener noreferrer" attribute (on anchor tags) seems to work on the same origin as well?
Should I use both? They seem to be quite complimentary.... Some advice here would be nice.
Most helpful comment
F2F notes thanks to @arturjanc. Unfortunately @csreis's feedback did not get discussed. A summary of the headers we did discuss:
It seems if we decide that OAuth needs to be L1-protected we can remove the
[ RWS outgoing ]bit. (So every window opened is as ifnoopenerwas specified.)A special thing with
Upgrade-No-CORSnot discussed at the meeting is that it would have to inherit into dedicated workers as well (it already inherits into sameness nested browsing contexts). Other workers are never in the same agent cluster so they need to have it set on them specifically (also, inheritance would be racy). (This header is also independently useful for things like the memory API. Perhaps we can also bindnavigator.storage.estimate()to it...)Cross-Opener-Policyis probably the hardest to define given the generally unsound infrastructure, but probably still doable. I don't know if anyone was planning on working on this, but I'd be happy to help out.Feedback on this from @whatwg/security is welcome.