1. Introduction
In early 2018, Spectre made it clear that a foundational security boundary the web aimed to maintain was substantially less robust than expected. [SPECTRE] This revelation has pushed web browsers to shift their focus from the platform-level origin boundary to an OS-level process boundary. Chromium’s threat model, for instance, now asserts that "active web content … will be able to read any and all data in the address space of the process that hosts it". [POST-SPECTRE-RETHINK] This shift in thinking imposes a shift in development practice, both for browser vendors, and for web developers. Browsers need to align the origin boundary with the process boundary through fundamental refactoring projects (for example, [SITE-ISOLATION] and [PROJECT-FISSION]). Moreover, browsers must provide web developers with tools to mitigate risk in the short term, and should push the platform towards safe default behaviors in the long term. The bad news is that this is going to be a lot of work, much of it falling on the shoulders of web developers. The good news is that a reasonable set of mitigation primitives exists today, ready and waiting for use.
This document will summarize the threat model which the Web Application Security Working Group espouses, point to a set of mitigations which seem promising, and provide concrete recommendations for developers responsible for protecting users' data.
1.1. Threat Model
Spectre-like side-channel attacks inexorably lead to a model in which active web content (JavaScript, WASM, probably CSS if we tried hard enough, and so on) can read any and all data which has entered the address space of the process which hosts it. While this has deep implications for user agent implementations' internal hardening strategies (stack canaries, ASLR, etc), here we’ll remain focused on the core implication at the web platform level, which is both simple and profound: any data which flows into a process hosting a given origin is legible to that origin. We must design accordingly.
In order to determine the scope of data that can be assumed accessible to an attacker, we must make a few assumptions about the normally-not-web-exposed process model which the user agent implements. The following seems like a good place to start:
-
User agents are capable of separating the execution of a web origin’s code into a process distinct from the agent’s core. This separation enables the agent itself to access local devices, fetch resources, broker cross-process communication, and so on, in a way which remains invisible to any process potentially hosting untrusted code.
-
User agents are able to make decisions about whether or not a given resource should be delivered to a process hosting a given origin based on characteristics of both the request and the response (headers, etc).
-
User agents can consistently separate top-level, cross-origin windows into distinct processes. They cannot consistently separate same-site or same-origin windows into distinct processes given the potential for synchronous access between the windows.
-
User agents cannot yet consistently separate framed origins into processes distinct from their embedders' origin.
Note: Though some user agents support out-of-process frames [OOPIF], no agent supports it consistently across a broad range of devices and platforms. Ideally this will change over time, as the frame boundary must be one we can eventually consider robust.
With this in mind, our general assumption will be that an origin gains access to any resource which it renders (including images, stylesheets, scripts, frames, etc). Likewise, embedded frames gain access to their ancestors' content.
[COI-THREAT-MODEL] spells out more implications. Bring them in here for more nuance.
1.2. TL;DR
-
Decide when (not!) to respond to requests by examining incoming headers, paying special attention to the
Origin
header on the one hand, and variousSec-Fetch-
prefixed headers on the other, as described in [resource-isolation-policy]. -
Restrict attackers' ability to load your data as a subresource by setting a cross-origin resource policy (CORP) of
same-origin
(opening up tosame-site
orcross-origin
only when necessary). -
Restrict attackers' ability to frame your data as a document by opt-ing into framing protections via
X-Frame-Options: SAMEORIGIN
or CSP’s more granular frame-ancestors directive (frame-ancestors 'self' https://trusted.embedder
, for example). -
Restrict attackers' ability to obtain a handle to your window by setting a cross-origin opener policy (COOP). In the best case, you can default to a restrictive
same-origin
value, opening up tosame-origin-allow-popups
orunsafe-none
only if necessary. -
Prevent MIME-type confusion attacks and increase the robustness of passive defenses like cross-origin read blocking (CORB) / opaque response blocking ([ORB]) by setting correct
Content-Type
headers, and globally assertingX-Content-Type-Options: nosniff
.
Describe these mitigations in more depth, swiping liberally from Notes on the threat model of cross-origin isolation, Safely reviving shared memory, etc.
2. Practical Examples
2.1. Subresources
Resources which are intended to be loaded into documents should protect themselves from being used in unexpected ways. Before walking through strategies for specific kinds of resources, a few headers seem generally applicable:
-
Sites should use Fetch Metadata to make good decisions about when to serve resources, as described in [resource-isolation-policy]. In order to ensure that decision sticks, servers should explain its decision to the browser by sending a
Vary
header containingSec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site, Sec-Fetch-User
. This ensures that the server has a chance to make different decisions for requests which will be used differently. -
Subresources should opt-out of MIME type sniffing by sending an
X-Content-Type-Options
header with a value ofnosniff
. This increases the robustness of MIME-based checks like cross-origin read blocking (CORB) / opaque response blocking ([ORB]), and mitigates some well-known risks around type confusion for scripts. -
Subresources are intended for inclusion in a given context, not as independently navigable documents. To mitigate the risk that navigation to a subresource causes script execution or opens an origin up to attack in some other way, servers can assert the following set of headers which collectively make it difficult to meaningfully abuse a subresource via navigation:
-
Using the
Content-Security-Policy
header’s to assert thesandbox
directive ensures that these resources remain inactive if navigated to directly as a top-level document. No scripts will execute, and the resource will be pushed into an opaque origin.Note: Some servers deliver
Content-Disposition: attachment; filename=file.name
to obtain a similar effect. This was valuable to mitigate vulnerabilities in Flash, but the sandbox approach seems to more straightforwardly address the threats we care about today. -
Asserting the
Cross-Origin-Opener-Policy
header with a value ofsame-origin
prevents cross-origin documents from retaining a handle to the resource’s window if it’s opened in a popup. -
The
X-Frame-Options
header with a value ofDENY
prevents the resource from being framed.
-
Most subresources, then, should contain the following block of headers, which you’ll see repeated a few times below:
Content-Security-Policy: sandbox Cross-Origin-Opener-Policy: same-origin Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site, Sec-Fetch-User X-Content-Type-Options: nosniff X-Frame-Options: DENY
With these generic protections in mind, let’s sift through a few scenarios to determine what headers a server would be well-served to assert:
2.1.1. Static Subresources
By their nature, static resources contain the same data no matter who requests them, and therefore cannot contain interesting information that an attacker couldn’t otherwise obtain. There’s no risk to making these resources widely available, and value in allowing embedders to robustly debug, so something like the following response headers could be appropriate:
Access-Control-Allow-Origin: * Cross-Origin-Resource-Policy: cross-origin Timing-Allow-Origin: * Content-Security-Policy: sandbox Cross-Origin-Opener-Policy: same-origin X-Content-Type-Options: nosniff X-Frame-Options: DENY
Note: Purely static resources always respond with the same data, no matter the request. There’s
therefore little benefit to sending a Vary
header: it can be safely omitted for these responses.
CDNs are the canonical static resource distribution points, and many use the pattern above. Take a look at the following common resources' response headers for inspiration:
-
https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js
-
https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js
-
https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js
Similarly, application-specific static resource servers are a good place to look for this practice. Consider:
-
https://static.xx.fbcdn.net/rsrc.php/v3/y2/r/zVvRrO8pOtu.png
-
https://www.gstatic.com/images/branding/googlelogo/svg/googlelogo_clr_74x24px.svg
2.1.2. Dynamic Subresources
Subresources that contain data personalized to a given user are juicy targets for attackers, and must be defended by ensuring that they’re loaded only in ways that are appropriate for the data in question. A few cases are well worth considering:
-
Application-internal resources (private API endpoints, avatar images, uploaded data, etc.) should not be available to any cross-origin requestor. These resources should be restricted to usage as a subresource in same-origin contexts by sending a
Cross-Origin-Resource-Policy
header with a value ofsame-origin
:Cross-Origin-Resource-Policy: same-origin Content-Security-Policy: sandbox Cross-Origin-Opener-Policy: same-origin Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site, Sec-Fetch-User X-Content-Type-Options: nosniff X-Frame-Options: DENY
This header will prevent cross-origin attackers from loading the resource as a response to a
no-cors
request.For example, examine the headers returned when requesting endpoints like the following:
-
Personalized resources intended for cross-origin use (public API endpoints, etc) should carefully consider incoming requests' properties before responding. These endpoints can only safely be enabled by requiring CORS, and choosing the set of origins for which a given response can be exposed by setting the appropriate access-control headers, for example:
Access-Control-Allow-Credentials: true Access-Control-Allow-Origin: https://trusted.example Access-Control-Allow-Methods: POST Access-Control-Allow-Headers: ... Access-Control-Allow-...: ... Cross-Origin-Resource-Policy: same-origin Content-Security-Policy: sandbox Cross-Origin-Opener-Policy: same-origin Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site, Sec-Fetch-User X-Content-Type-Options: nosniff X-Frame-Options: DENY
Note: The
Cross-Origin-Resource-Policy
header is only processed for requests that are _not_ using CORS for access control ("no-cors
requests"). SendingCross-Origin-Resource-Policy: same-origin
is therefore not harmful, and works to ensure thatno-cors
usage isn’t accidentally allowed.For example, examine the headers returned when requesting endpoints like the following:
-
Personalized resources that are intended for cross-origin
no-cors
embedding, but which don’t intend to be directly legible in that context (avatar images, authenticated media, etc). These should enable cross-origin embedding viaCross-Origin-Resource-Policy
, but _not_ via CORS access control headers:Cross-Origin-Resource-Policy: cross-origin Content-Security-Policy: sandbox Cross-Origin-Opener-Policy: same-origin Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site, Sec-Fetch-User X-Content-Type-Options: nosniff X-Frame-Options: DENY
Note: That this allows the resource to be used by any cross-origin document. That’s reasonable for some use cases, but requiring CORS, and opting-in a small set of origins via appropriate access-control headers is a possible alternative for some resources. This approach will give those contexts trivial access to the resource’s bits, so the granularity is a tradeoff. Still, considering this case to be the same as the "personalized resources intended for cross-origin use" isn’t unreasonable.If we implemented more granular bindings for CORP headers (along the lines of
Cross-Origin-Resource-Policy: https://trusted.example
), we could avoid this tradeoff entirely. <https://github.com/whatwg/fetch/issues/760>For example:
2.2. Documents
2.2.1. Fully-Isolated Documents
Documents that require users to be signed-in almost certainly contain information that shouldn’t be revealed to attackers. These pages should take care to isolate themselves from other origins, both by making _a priori_ decisions about whether to serve the page at all (see [resource-isolation-policy], for example), and by giving clients careful instructions about how the page can be used once delivered. For instance, something like the following set of response headers could be appropriate:
Cross-Origin-Opener-Policy: same-origin Cross-Origin-Resource-Policy: same-origin Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site, Sec-Fetch-User X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN
Note: Documents which need to make use of APIs that require full cross-origin isolation (such
as SharedArrayBuffer
), will also need to serve a Cross-Origin-Embedder-Policy
header, as
outlined in [coop-coep] and [cross-origin-isolation-guide].
Account settings pages, admin panels, and application-specific documents are all good examples of resources which would benefit from as much isolation as possible. For real-life examples, consider:
2.2.2. Documents Expecting to Open Cross-Origin Windows
Not every document that requires sign-in can be fully-isolated from the rest of the internet. It’s
often the case that partial isolation is a better fit. Consider sites that depend upon cross-origin
windows for federated workflows involving payments or sign-in, for example. These pages would
generally benefit from restricting attackers' ability to embed them, or obtain their window handle,
but they can’t easily lock themselves off from all such vectors via Cross-Origin-Opener-Policy: same-origin
and X-Frame-Options: DENY
. In these cases, something
like the following set of response headers might be appropriate:
Cross-Origin-Opener-Policy: same-origin-allow-popups Cross-Origin-Resource-Policy: same-origin Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site, Sec-Fetch-User X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN
The only difference between this case and the "Fully-Isolated" case above is the Cross-Origin-Opener-Policy
value. same-origin
will break the opener relationship between the
document and any cross-origin window, regardless of who opened whom. same-origin-allow-popups
will break cross-origin opener relationships initiated by a cross-origin
document’s use of window.open()
, but will allow the asserting document to open cross-origin
windows that retain an opener relationship.
2.2.3. Documents Expecting Cross-Origin Openers
Federated sign-in forms and payment providers are clear examples of documents which intend to be
opened by cross-origin windows, and require that relationship to be maintained in order to
facilitate communication via channels like postMessage(message, options)
or navigation.
These documents cannot isolate themselves completely, but can prevent themselves from being embedded
or fetched cross-origin. Three scenarios are worth considering:
-
Documents that only wish to be opened in cross-origin popups could loosen their cross-origin opener policy by serving the following headers:
Cross-Origin-Resource-Policy: same-origin Cross-Origin-Opener-Policy: unsafe-none Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site, Sec-Fetch-User X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN
For example:
-
Documents that only wish to be framed in cross-origin contexts could loosen their framing protections by serving the following headers:
Cross-Origin-Resource-Policy: same-origin Cross-Origin-Opener-Policy: same-origin Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site, Sec-Fetch-User X-Content-Type-Options: nosniff X-Frame-Options: ALLOWALL
Note: That this allows embedding by any cross-origin documents. That’s reasonable for some widgety use cases, but when possible, a more secure alternative would specify a list of origins which are allowed to embed the document via the frame-ancestors CSP directive. That is, in addition to theX-Frame-Options
header above, the following header could also be included to restrict the document to a short list of trusted embedders:Content-Security-Policy: frame-ancestors https://trusted1.example https://trusted2.example
For example:
-
Documents that support both popup and framing scenarios need to loosen both their cross-origin opener policies and framing protections by combining the recommendations above, serving the following headers:
Cross-Origin-Resource-Policy: same-origin Cross-Origin-Opener-Policy: unsafe-none Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site, Sec-Fetch-User X-Content-Type-Options: nosniff X-Frame-Options: ALLOWALL
For example:
3. Implementation Considerations
3.1. Explicitly Setting Headers with Default Values
Several recommendations above suggest that developers would be well-served to set headers like X-Frame-Options: ALLOWALL
or Cross-Origin-Opener-Policy: unsafe-none
on responses. These
map to the web’s status quo behavior, and seem therefore superfluous. Why should developers
set them?
The core reason is that these defaults are poor fits for today’s threats, and we ought to be working to change them. Proposals like [EMBEDDING-REQUIRES-OPT-IN] and [COOP-BY-DEFAULT] suggest that we shift the web’s defaults away from requiring developers to opt-into more secure behaviors by making them opt-out rather than opt-in. This would place the configuration cost on those developers whose projects require risky settings.
This document recommends setting those less-secure header values explicitly, as that makes it more likely that we’ll be able to shift the web’s defaults in the future.
3.2. Isolating Local-Scheme Frames
Note that frames loaded from local schemes will generally inherit policies applied to the document
which created them, and may end up in-process with that document if the stars align unfortunately.
Developers are encouraged to explicitly shift these documents to opaque origins, either by using data:
URLs directly, or by applying a sandbox
attribute to frames created using <iframe srcdoc="...">
, blob:
URLs, and so on.
Likewise, user agents are encouraged to take sandbox
attributes into account when
allocating processes for framed documents, and to align the process boundary with the origin
boundary whenever possible.
4. Acknowledgements
This document relies upon a number of excellent resources that spell out much of the foundation of our understanding of Spectre’s implications for the web, and justify the mitigation strategies we currently espouse. The following is an incomplete list of those works:
[APPLICATION-PRINCIPALS], [LONG-TERM-MITIGATIONS], [SPECTRE-SHAPED-WEB], [POST-SPECTRE-RETHINK], [SPILLING-THE-BEANS], [CROSS-ORIGIN-EMBEDDER-POLICY], [CROSS-ORIGIN-OPENER-POLICY-EXPLAINER], [COOP-COEP-EXPLAINED], [SAFELY-REVIVING-SHARED-MEMORY], [COI-THREAT-MODEL]