Skip to content

Instantly share code, notes, and snippets.

@yoavweiss
Last active June 18, 2024 08:59
Show Gist options
  • Save yoavweiss/c7b61e97e6f8d207be619f87ab96ead5 to your computer and use it in GitHub Desktop.
Save yoavweiss/c7b61e97e6f8d207be619f87ab96ead5 to your computer and use it in GitHub Desktop.

noopener-allow-popups Cross-Origin-Opener-Policy value

Some origins can contain different applications with different levels of security requirements. In those cases, it can be beneficial to prevent scripts running in one application from being able to open and script pages of another same-origin application. Such a document need to ensure its opener cannot script it, even if the opener document is a same-origin one.

The noopener-allow-popups Cross-Origin-Opener-Policy value severs the opener relationship between the document loaded with this policy and its opener. At the same time, the opened document can open further documents (as the "allow-popups" in the name suggests) and maintain its opener relationship with them, assuming that their COOP policy allows it.

The problem

Let's examine a hypothetical example.com origin that has two very different applications. One is https://example.com/chat that enables any user to contact any other user and send them messages. Another is https://example.com/passwords that contains all of the user's passwords, across different services.

Common security advice to further isolate these applications would be to host them on different origins. But in some cases that's not possible, and those two applications have to be on a single origin for historical/business/branding reasons.

At the same time, the administrators of the "passwords" application would very much like to ensure that it can't be directly scripted by the "chat" app, which by its nature has a larger XSS surface.

So they ask themselves the following question: if we assume that "chat" was compromised, what can prevent the attacker from reading the contents of "passwords"?

They can prevent fetch() calls on "chat" from fetching the "passwords" application HTML directly, by looking at Sec-Fetch-Mode and ensuring its value is "navigate". They can prevent "passwords" from being iframed by "chat" (or anyone) by adding an X-Frame-Options: deny or Content-Security-Policy: frame-ancestors 'none'.

But currently, they can't prevent "passwords" from being opened as a popup or a new tab, and then be scripted by the opener. If that happens, the opener has full access to the "passwords" DOM, and can use that to extract secrets from it!!

Solution: COOP: noopener-allow-popups

By providing a header that enables a document to severe its incoming opener relationship regardless of origin, we can prevent that above abuse scenario.

The opener document who would call window.open() would get back a WindowProxy that would change its closed value to true as soon as the openee document gets assigned to a browsing context group, similar to e.g. a window.open() to a document that redirects across origins.

@ddworken
Copy link

While this proposal seems reasonable to me, I want to call out that trying to provide isolation between two same-origin pages is rather complex to get right and not something that the web platform usually tries to provide. So a quick list of the other risks that will have to be addressed in order to ensure that two same-origin pages are actually isolated from each other:

  1. (As mentioned above) Using Fetch Metadata to block same-origin requests.
  2. (As mentioned above) Using X-Frame-Options or CSP frame-ancestors to block same-origin iframing.
  3. (As supported by this proposal) Severing the page connection for all popups.
  4. Ensuring all authentication credentials aren't accessible to JS (e.g. by using httponly cookies) and that there isn't anything sensitive stored in origin-scoped storage like localStorage.
  5. Ensuring that the lower sensitivity application doesn't install any service workers that also apply to the higher sensitivity application.
  6. Ensuring that there aren't any postMessage or BroadcastChannel based messaging schemes that expose information on a same-origin basis.
  7. Ensuring that the login page is served on a separate origin (since password managers auto-fill passwords with zero user-interaction, based on the origin). Also, accepting that with user interaction the user may be convinced to auto-fill their password anyways if the login form is same-site.
  8. Accepting that process isolation will still put the separate applications in the same process such that Spectre or a renderer exploit could be used to steal information.

Overall, it does seem like this is possible to get right with a limited threat-model, but it is a somewhat long list of things that have to all be done 100% correctly in order to provide isolation between two same-origin pages.

cc: @arturjanc @terjanq @shhnjk

@yoavweiss
Copy link
Author

Thanks! This is super useful :)

@arturjanc
Copy link

Assuming we ship this, it would IMO be good to document all of this in the spec to make sure that folks using this COOP mode are aware of these bypasses and required application-level protections. /cc @ArthurSonzogni

For the process model issue in #8 I think we might want to recommend for the browser to separate processes of documents with this value from other same-origin/site renderers (I know the spec doesn't define the process model, but we could add a comment). Otherwise, Spectre-like bugs provide a general bypass of the security properties we want to offer here, i.e. a same-origin XSS could read the data in a document protected with COOP noopener-allow-popups.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment