Origin isolation refers to segregating cross-origin documents into separate agent clusters. Translated into developer-observable effects, this means:
- preventing the
document.domainsetter from relaxing the same-origin policy; and - preventing
WebAssembly.Modules from being shared with cross-origin (but same-site) documents.
If a developer chooses to give up these capabilities, then the browser has more flexibility in how it allocates resources like processes, threads, and event loops.
Note: this proposal does not fully "isolate" the origin in every sense of the word. It focuses on agent cluster allocation issue. See below for potential future work on further isolation.
Note: a previous version of this proposal was based on origin policy, but we have now decoupled them. See below for more on this subject.
- Example
- Objectives
- Implementation strategies
- How it works
- Design choices and alternatives considered
- Adjacent work
- Potential future work
The delivery mechanism for a web application to achieve origin isolation is a new Origin-Isolation response header:
HTTP/1.1 200 OK
Content-Type: text/html
...
Origin-Isolation: ?1
...
The presence of the header, with the structured headers boolean "true" value ?1, indicates that any documents derived from that origin should be separated from other cross-origin documents—even if those documents are same site. In terms of observable, specified consequences for web developers, this means:
- Attempts to set
document.domainwill do nothing, so cross-origin documents will not be able to synchronously script each other, even if they are same site. - Attempts to share
WebAssembly.Modules to cross-origin documents viapostMessage()will fail, even if those documents are same site.
(Note that these are only observable consequences in document contexts; for workers, origin isolation has no effect.)
Goals:
- Allow web applications to isolate themselves from cross-origin synchronous scripting (in a guaranteed way).
- Allow user agents to use various implementation strategies to isolate origins when circumstances allow, and the developer has opted in to origin isolation, thus potentially achieving benefits for performance, security, and memory metrics.
- Ensure consistent web-developer-observable behavior regardless of implementation choices (such as process allocation) or the hints expressed by an origin. (Modulo side channels.)
- Avoid situations where two same-origin pages with different isolation preferences are treated as cross-origin by the browser. (See below.)
Non-goals:
- Mandate process isolation or other implementation choices for user agents.
- Give web developers visibility into process allocation.
Non-goals for now:
- "Fully" isolate an origin from all other origins, e.g. by preventing sharing of data via site-scoped storage or navigation. See below for potential future work in that direction.
- Allow web applications to easily configure origin isolation for all URLs on their origin. See below for potential future work in that direction.
- Allow web applications to provide information to the browser about why they want isolation, in order to better guide the browser's implementation strategies. See below for potential future work in that direction.
Currently, the state of the art in isolation is process-based. That is, current browser implementation strategies would likely place each origin-isolated origin in its own process. But it's important for applications to recognize that this header does not directly control process allocation, or other behind-the-scenes implementation strategies.
For example, in some cases allocating a process-per-isolated-origin is infeasible, e.g. because you are operating on a low-memory device, or have already allocated a ton of processes, or because the browser does not implement the ability for iframes to be placed in a separate process. In such scenarios the JavaScript-observable effects of isolation will still be present, giving applications an increase in encapsulation. But there wouldn't be any performance or security benefits.
Alternate isolation techniques are possible, which give some but not all of the benefits as process isolation. For example, Blink is exploring a "multiple Blink isolates/threads" project, which would provide parallelism and isolated heaps, but not full side-channel protection. But, these remain speculative as of the time of this writing.
A future extension might be able to help the browser better prioritize when to allocate processes, or choose between process isolation and other techniques.
An important concept in the browser processing model is the agent cluster. Agent clusters are how the specification expresses which realms (windows/workers/etc.) are able to share SharedArrayBuffer, WebAssembly.Memory, and WebAssembly.Module instances. They also constrain which pages can potentially synchronously script each other, e.g. via someIframe.contentWindow.someFunction().
The HTML Standard currently keys agent clusters on scheme-and-registrable-domain ("site"), which roughly means that any documents within the same browsing context group that are same-site can potentially synchronously script each other. Examples:
https://sub1.example.org/could script anhttps://sub2.example.org/iframe. It could not script ahttp://sub1.example.org/iframe (mismatched scheme).https://example.com/could script anhttps://example.com/popup that it opens. It could not script a completely separatehttps://example.com/tab, nor a popup opened withnoopener(mismatched browsing context group).https://whatwg.github.io/could not scripthttps://jsdom.github.io/, becausegithub.iois on the Public Suffix List.
Moving from the spec to the implementation level, the agent cluster boundary generally constrains how an implementation performs process separation. That is, since the specification mandates certain documents be able to access each other synchronously, implementations are constrained to put them in the same process. (Cross-origin same-site pages need to both set document.domain in order to allow such synchronous scripting, but the very fact that they could do so at any time constrains implementation choices.)
This means that the current state of the art in terms of segregating web content into different processes is site isolation, which segregates documents into separate processes according to the agent cluster rules. This can be done with no observable effects for JavaScript code.
Moving to origin isolation requires some observable changes, and thus the opt-in we propose here.
The specification for this feature in itself consists of two parts. One part is header parsing, which relies on the Structured Headers specification to do most of the work. The other is agent cluster keying, which is done by modifying the HTML Standard's agent cluster map.
In particular, when creating a document, we modify the algorithm to obtain an agent cluster key. It takes as input an origin of the realm to be created, requestsIsolation (a boolean, derived from the presence of a valid Origin-Isolation header), and a browsing context group group. It outputs the agent cluster key to be used, which will be either a site or an origin.
The new algorithm will check if origin has previously been placed in a site- or origin-keyed agent cluster within group, and if so, it will always return that same key, to stay consistent. Otherwise, it uses the requestsIsolation boolean to determine whether to return origin, or the derived site.
The consistency part of this algorithm has two interesting features:
- Even if origin isolation is not requested for a particular document creation, if origin isolation has previously created an origin-keyed agent cluster, then we put the new document in that origin-keyed agent cluster.
- Even when origin isolation is requested, if there has previously been a site-keyed agent cluster with same-origin documents in the browsing context group, then we put the new document in that site-keyed agent cluster, ignoring the isolation request.
Both of these are consequences of a desire to ensure that same-origin sites do not end up isolated from each other, even in scenarios involving navigating back and forward.
You can see a more full analysis of what results this algorithm produces in our scenarios document.
As far as the web-developer–observable consequences of using origins for agent cluster keys, this will automatically cause WebAssembly.Module instances to no longer be shareable cross-origin, because of the agent cluster check in StructuredDeserialize. We'd also need to add a check to the document.domain setter to make it no-op if the surrounding agent's agent cluster is origin-keyed.
Will origin isolation necessarily increase parallelism? The answer is no; parallelism remains an implementation choice, and is not forced by changing the agent cluster allocation. This is because the relevant specifications are structured to allow non-parallelized implementations, even across totally separate agents. The JavaScript specification says
an executing thread may be used as the executing thread by multiple agents
and gives the example of a web browser sharing a single thread across multiple unrelated tabs in a browser window.
In general, the only thing in the specification ecosystem that forces parallelism is workers, via their dedicated executing thread and the requirements around forward progress and the atomics APIs. So, although origin isolation could incidentally result in better parallelism as part of how implementations respond behind the scenes, parallelism is not automatically forced.
Does this proposal have any effect on sharing of SharedArrayBuffer or WebAssembly.Memory? The answer is no, but the reasoning is somewhat subtle.
The current specification consensus is that SharedArrayBuffer or WebAssembly.Memory will only be structured-serializable when the surrounding agent's agent cluster is cross-origin isolated, which means that COOP + COEP headers have been set. But cross-origin isolation automatically implies origin isolation; that is, cross origin isolation means that SharedArrayBuffer and WebAssembly.Memory will not be able to be sent cross-origin anyway. See below for more discussion on those two headers, and on the relationship between cross-origin isolation and origin isolation.
(Note that currently in Chromium on desktop platforms, this specification consensus is not yet followed, and SharedArrayBuffer and WebAssembly.Memory can be sent to cross-origin same-site destinations within the same agent cluster. The plan is for origin isolation to restrict this. But this is a temporary, desktop-Chromium-specific situation, and does not impact the specification proposal.)
Finally, we turn our attention to WebAssembly.Module. Structured serialization of WebAssembly.Module is restricted to agent clusters, but not for security reasons: the restriction is in place because the point of structured serializing a WebAssembly.Module is to avoid recompilation of the module, which is only possible if its destination is within the same process. As such, sending WebAssembly.Module around is not guarded by COOP + COEP, and so origin isolation will observably change the boundaries within which it can be sent, by changing the scope of agent clusters to be origin-keyed instead of site-keyed. This is why, in addition to the synchronous scripting restrictions, we mention the changes to WebAssembly.Module serialization as an observable consequence of origin isolation.
We mentioned above that origin isolation has no effect on workers. Here we explain why exactly that is.
First note that shared and service workers are already isolated into their own agent clusters. The only other things that exist in those agent clusters are potential nested dedicated workers, which are already restricted to being same-origin.
What remains to consider is dedicated workers which are spawned directly by documents. Those end up in the agent cluster of their owner document.
Does the owner document's Origin-Isolation header impact any code inside such a decicated worker? The answer is no. All access to other realms is asynchronous inside a dedicated worker, so document.domain does not apply. And there's no way for dedicated worker code to send a WebAssembly.Module to a same-site cross-origin destination directly, because the only thing it can communicate with is its parent document, or further nested dedicated workers, all of which are same-origin. Note that even BroadcastChannel, which bypasses the need to have a direct reference to the destination, is restricted to same-origin communications.
A worker could send a WebAssembly.Module to a same-site cross-origin destination by first passing it (or a MessageChannel through which the WebAssembly.Module gets transmitted) to a same-origin document, and letting the document try to go beyond the origin boundary. This would no longer be possible with this proposal, but only because of the proposal's effect on the document, not because of any effect it had on the worker.
This proposal follows in the tradition of other security primitives like CSP, COEP, and COOP, by using a header to change the behavior of the document.
A previous iteration of this proposal used origin policy, in an attempt to ensure that all documents on the origin are aligned behind the decision to be isolated. We believe this is still a desirable direction, both for the Origin-Isolation header and for other security headers. But it's worth noting that the advantages are mainly in easing server configuration. Because different page loads on an origin could recieve different origin policies anyway (due to, e.g., server-side updates), there is no technical simplification gained by using origin policy. That is, the browser and site developer need to deal with the potential for varying origin isolation preferences across the site in both delivery mechanisms.
See below for how this proposal might be upgraded to use origin policy in the future.
The scenarios document explains how the current specification plan works in tricky scenarios involving different Origin-Isolation headers between loads of realms from a given origin. In short, origins get isolated per browsing context group.
We considered two other possible models:
- Per-realm: in this model, we would not check for the existence of other site-keyed realms within the browsing context group, and would always origin-isolate realms which request isolation, and never isolate ones that do not.
- Per-user agent: in this model, we would keep a global list of all origins which have ever requested isolation, and consult this first whenever creating a new realm.
The downside of the per-realm model is that it leads to strange scenarios like https://b.example.com/1 and https://b.example.com/2 being in different agent clusters: one of them site-keyed, and the other origin-keyed. This is not desirable, as same-origin pages should not be isolated from each other by the origin isolation feature.
The downside of the per-user agent model is that it makes it very hard for web application developers to roll back origin isolation. They essentially have to communicate to the user to restart their browser.
Why did we choose to invent a new header for origin isolation, instead of using feature policy or document policy?
It turns out, both of these mechanisms are designed for dealing with embedded content, and as such have inheritance models that don't make much sense for origin isolation. If you want to isolate your own origin, that doesn't necessarily mean anything about how your subframes should behave—especially considering that the effects of feature/document policy are not limited to same-origin, or even same-site, subframes.
Another potential road we explored was to try to find scenarios where a process-per-origin could be done transparently, without an explicit opt-in. This would be possible if:
- We restricted
WebAssembly.Moduleto same-origin usage at all times. (It's currently unknown if doing this would be web-compatible.) - Pages used the
"document-domain"feature policy to turn off their own ability to usedocument.domain.
Under these conditions, origin-based process isolation could be done by user agents unobservably, similar to how site-based process isolation is done today.
We avoided this approach because of the complexities involved in the use of the "document-domain" feature policy. In particular, it can only allow isolation if all documents in a browsing context group impose the policy upon themselves. Since feature policy is a per-document, not per-origin or per-browsing context group configuration, this is hard to guarantee. What if a new page joins the browsing context group, which does not impose the feature policy? There are workarounds, e.g. changing the semantics of "document-domain" so that its default allowlist depends on other pages in the browsing context group, but these end up making the "document-domain" feature policy no longer mean "disable document.domain", but instead have a bunch of other side effects and action at a distance.
The proposal here is to make the document.domain setter do nothing (perhaps emitting a console warning) when used. Throwing an exception would be more semantically correct: it is unable to do the operation you are requesting.
However, throwing would pose a potential migration burden for sites. Measurements from Chrome show that the document.domain setter is widely used (~12% of page views), but only has an effect a smaller percentage of the time (~0.28% + ~0.52% of page views). If we made it throw, then all of that 12% would need to update their code before they could take advantage of origin isolation. By making it do nothing, we instead confine the migration cost to the <1%.
The Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers, when combined, also give origin isolation. That is, documents which include both headers will automatically change their agent cluster key to use the origin, instead of the site. As discussed previously, this has observable effects on document.domain and WebAssembly.Module. Note that, in contrast to the problems explained above with using feature/document policy, this model works, since COEP is required to match for all descendant browsing contexts anyway, and COOP is consulted via the top-level browsing context.
We are optimistic about this approach for getting the observable effects of origin isolation. In particular, preventing the detrimental effects of document.domain is a long-time goal of the web standards and browser community, and tying that to headers like COOP + COEP and the various carrots they unlock seems like a great strategy.
However, we think that there remains room for this separate origin isolation proposal. In particular:
-
Deploying
COOP+COEPcan be a significant burden for sites, e.g. in terms of how it requires all of their embedded content to be updated withCORP,COEP, andCOOPheaders. Some sites may not want to use any of the features enabled byCOOP+COEP(such asSharedArrayBufferorprocess.measureMemory()), and so be unmotivated to take on this burden. But they still might want origin isolation, either for increased encapsulation, or hoping for performance and security benefits. In such cases, this separate header would be much easier to deploy thanCOOP+COEP, since it does not require any embeddee cooperation. -
Turning on
COOP+COEPdoes not provide a clear signal that the origin desires origin isolation; it's instead a technique for ensuring that everything in your process opts in to being there. As such, the presence of a more explicit signal could help the browser decide whether or not to consolidate processes. For example, consider aCOOP+COEP-using site with 30 ad-containing iframes, also usingCOOP+COEP. By default, browsers are likely to place all 31 origins into the same process, since they have all appropriately opted in. But if the top-level site had anOrigin-Isolationheader, then the browser could take this as a hint to separate the top-level site into one process, and the ad iframes into another.
So to conclude, the separate Origin-Isolation header can be used both by sites who are not yet using COOP + COEP, to get origin isolation without taking on the burden of cross-origin isolation, and it can be used by sites that are using COOP + COEP, to provide the browser with a more explicit signal.
A previous version of this proposal included the ability for the web application to specify hints as to why it was requesting origin isolation, to better guide the browser in its resource allocation decisions. For example, a site could use
Origin-Isolation: paralllelism
to indicate that it was mostly interested in isolating itself to gain better parallelism with same-site cross-origin pages that it would otherwise share resources with. Or a page could use
Origin-Isolation: memory-measurement
to indicate that the reason it was isolating itself was to get tighter-scoped results from the performance.measureMemory() API.
We are setting aside this ability for now due to an initial lack of implementer interest. You can see the deliberation process for the Chromium browser in this document. However, we are happy to add this feature back if any implementation has concrete plans for how they would use the hints. For example, in Chromium we will be closely monitoring whether excessive use of the Origin-Isolation header causes us to want hints to help prioritize our process allocation decisions. And we will reevaluate the role of hints when the multiple Blink isolates/multiple Blink threads projects get closer to shipping.
A previous version of this proposal relied on origin policy. At this time, however, there remain a number of open design questions about origin policy, some with no clear path toward a solution. So the plan is to decouple origin isolation from origin policy for now, instead using the Origin-Isolation header design seen throughout the rest of this document.
That said, we believe that origin policy would still be an optimal deployment vehicle for many things currently delivered as headers. This includes not only Origin-Isolation, but also Content-Security-Policy, Feature-Policy, Document-Policy, Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy, and more. The same arguments as to why these would be best configured on an origin-wide basis apply to all of these headers. So for now, the plan is to develop origin isolation in the same way as the others, but also to add it to the list of candidates for integration into origin policy, once origin policy becomes ready.
For reference, a sample origin policy manifest expressing similar values to the header would be something like
{
"ids": ["my-policy"],
"isolation": true
}As noted earlier in this document, this proposal is focused narrowly on the agent cluster allocation issue, and its implications for synchronous cross-origin access which has various security and performance effects.
One might envision others was of isolating an origin that go beyond this. Many such concepts have been discussed in a previous "Isolation Explainer" from 2016. Some, like window.opener restriction or preventing cross-origin fetches, have since found solutions. (Respectively, Cross-Origin-Opener-Policy and Cross-Origin-Resource-Policy/Sec-Fetch-Site.) The ones that remain appear to be:
- Preventing the origin from sharing storage (of various types) with different origins, even if those origins are same-site. This would include things like cookies or Web Authentication entries.
- Double-keying cookies, so that when an isolated origin makes a request to
https://third-party.example/, this uses a separate cookie jar from when any other origin makes a request tohttps://third-party.example/. - Preventing navigations from other origins to this origin (or perhaps allowing only specific entry points as navigation targets).
We believe that, after tackling agent cluster allocation, these may be worthwhile further efforts to pursue. If so, building on top of the Origin-Isolation header seems very reasonable. One could imagine something like
Origin-Isolation: side-channel-protection, isolated-storage
as an extension of this proposal.