Origin-keyed agent clusters refers to segregating cross-origin documents into separate agent clusters. Translated into developer-observable effects, this means:
- preventing the
document.domain
setter from relaxing the same-origin policy; and - preventing
WebAssembly.Module
s 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.
Origin-keyed agent clusters only work in a secure context.
This proposal was merged into the HTML Standard in whatwg/html#5545 under the original name "origin isolation", and renamed in whatwg/html#6214. The source of truth for its specification is now in the HTML Standard. This repository remains, in archived form, to provide the full explainer and supplementary documents. Issues can be filed on whatwg/html.
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-keyed agent clusters is a new Origin-Agent-Cluster
response header:
HTTP/1.1 200 OK
Content-Type: text/html
...
Origin-Agent-Cluster: ?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.domain
will 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.Module
s to cross-origin documents viapostMessage()
will fail, even if those documents are same site. - The boolean
window.originAgentCluster
will be true, to provide an easy way of detecting these restrictions without having to try them.
For example, if https://example.com/
embedded https://sub.example.com/
as an iframe or opened it as a popup, these two documents would normally be able to synchronously script each other, after both ran the JavaScript code document.domain = "example.com"
. With origin-keyed agent clusters enabled, setting document.domain
would instead be a no-op.
Similarly, normally if https://example.com/
embedded https://sub.example.com/
as an iframe or opened it as a popup, they would be able to send each other WebAssembly.Module
instances using postMessage()
. With origin keying enabled, the postMessage()
call would instead throw an exception.
(Note that these are only observable consequences in document contexts; for workers, origin-keying of agent clusters has no effect.)
Goals:
- Allow web applications to prevent cross-origin synchronous scripting via
document.domain
(in a guaranteed way). - Allow user agents to use various implementation strategies to segregate origins when circumstances allow, and the developer has opted in to origin-keyed agent clusters, thus potentially achieving benefits for performance, security, and memory metrics.
- Ensure consistent web-developer-observable behavior regardless of implementation choices such as process allocation. (Modulo side channels.)
- Avoid situations where two same-origin pages with different agent cluster keying preferences are treated as cross-origin by the browser. (See below.)
- Allow web developers to introspect, using client-side scripting, whether their request for origin keying has been honored. (See below.)
Non-goals:
- Mandate process isolation or other implementation choices for user agents.
- Give web developers visibility into process allocation.
- Give guaranteed security benefits, of the sort that can enable powerful features.
COOP
+COEP
is more appropriate for that.
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-keying 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 origin-keying, 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-keyed 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-origin-keyed-agent-cluster 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 origin-keying 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.io
is 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.
If an implementation wants to segregate an origin into its own process, this requires some observable changes. Thus, the opt-in we propose here.
The specification for this feature in itself consists of three 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. And the final part is the window.originAgentCluster
boolean.
In particular, when creating a document, we modify the algorithm to obtain a similar-origin window agent. It takes as input an origin of the realm to be created, a browsing context group group, and requestsOAC (a boolean, derived from the presence of a valid Origin-Agent-Cluster
header on a secure context). It either returns a new or existing agent cluster, depending on these inputs.
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 agent cluster, to stay consistent. Otherwise, it uses the requestsOAC boolean to determine whether to return an origin-keyed agent cluster, or one keyed by the derived site.
The consistency part of this algorithm has two interesting features:
- Even if origin-keying is not requested for a particular document creation, if the user agent has previously created an origin-keyed agent cluster in the browsing context group, then we put the new document in that origin-keyed agent cluster.
- Even when origin-keying 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 origin-keying request.
Both of these are consequences of a desire to ensure that same-origin sites do not end up segregated from each other, even in scenarios involving navigating back and forward.
Because this means that it's possible for the Origin-Agent-Cluster
header to be present, but origin-keying restrictions to not apply, window.originAgentCluster
exists to allow web developers to detect these mismatches (which are probably the result of server misconfiguration). That getter will only return true
if the agent cluster is actually origin-keyed. See more below.
You can see a more full analysis of what results this algorithm produces in our scenarios document, or you can view the HTML Standard pull request.
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-keying 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-keying 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 keying; 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-keyed agent clusters.
(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-keyed agent clusters 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-keying 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-keyed agent clusters.
We mentioned above that origin keying has no effect on workers. Here we explain why exactly that is.
First note that shared and service workers are already segregated 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-Agent-Cluster
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.
"Origin-keyed agent clusters" sure is a mouthful, with lots of jargon that web developers are not usually exposed to. However, this is actually desirable.
A previous revision of this proposal used the name "origin isolation", with a header named Origin-Isolation
. Unfortunately, we found that in practice this gave the wrong impression: people thought that the header gave guaranteed process isolation (similar to Chrome's site isolation feature), or gave security benefits (similar to the cross-origin isolation feature discussed below). You can read more about this in whatwg/html#6192.
By using a jargon-full name, we are communicating that this feature is subtle in its effects. It also makes it clear that we are isolating in very specific ways, that do not touch on the other ideas discussed below.
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 origin-keyed. We believe this is still a desirable direction, both for the Origin-Agent-Cluster
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-keying 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-Agent-Cluster
headers between loads of realms from a given origin. In short, the keying is determined on a per-browsing context group basis.
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-key realms which request it, and never origin-key ones that do not.
- Per-user agent: in this model, we would keep a global list of all origins which have ever requested origin-keying, 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 segregated from each other by this feature.
The downside of the per-user agent model is that it makes it very hard for web application developers to roll back origin-keying. They essentially have to communicate to the user to restart their browser.
Why did we choose to invent a new header for origin-keyed agent clusters, 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 keying. If you want to origin-key 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.Module
to 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 origin keying 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-keyed agent clusters. By making it do nothing, we instead confine the migration cost to the <1%.
The window.originAgentCluster
getter can feel a bit redundant. After all, the web developer should know whether origin keying applies: they set the header themselves!
However, as explained above, the Origin-Agent-Cluster
header cannot always be respected. If a web developer has configured their server inconsistently, or if they are in the middle of changing their server configuration, other same-origin documents in the browsing context group might have different values for the header, and the overriding consistency requirement would then take over. Thus, window.originAgentCluster
gives runtime insight into whether the origin-keying request succeeded, which can be useful for reporting, or for detecting misconfigurations.
There are some subtleties around what this getter should return in various edge cases. As indicated by the name, it's currently designed to return true whenever origin keying is in play. This means that it always returns true
for pages with opaque origins, because for those cases the page's site is the same as its origin. I.e., those cases can be considered automatically origin-keyed, regardless of the header. This was discussed in more detail in #24 and #31, which contemplated other alternatives that give different answers in these edge cases. See also whatwg/html#5940.
Note that window.originAgentCluster
is present even in non-secure contexts. This maintains consistency with window.crossOriginIsolated
; both are useful for detecting what effects headers have on a page, even if the corresponding headers are only usable in secure contexts. (Also, note that in the edge cases mentioned above, the getter can return true
even in a non-secure context.)
Finally, there's the question as to whether the getter should be exposed in workers. Currently it is not, since as noted above, origin keying has no direct effects on workers, so we don't anticipate this being very useful. However, it is conceptually coherent to expose it; it would just always return true for shared/service workers, and would return the same value as the owner Window
for dedicated workers, indicating the indirect effect that the owner Window
has. We're open to doing so in the future if any use cases are provided.
The Cross-Origin-Opener-Policy
and Cross-Origin-Embedder-Policy
headers, when combined, also give origin-keyed agent clusters. 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 keying. 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-Agent-Cluster
proposal. In particular:
-
Deploying
COOP
+COEP
can be a significant burden for sites, e.g. in terms of how it requires all of their embedded content to be updated withCORP
,COEP
, andCOOP
headers. Some sites may not want to use any of the features enabled byCOOP
+COEP
(such asSharedArrayBuffer
orprocess.measureMemory()
), and so be unmotivated to take on this burden. But they still might want origin-keyed agent clusters, 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
+COEP
does not provide a clear signal that the origin desires origin keying; 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-Agent-Cluster
header, 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-agent-Cluster
header can be used both by sites who are not yet using COOP
+ COEP
, to get origin keying 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 keying, to better guide the browser in its resource allocation decisions. For example, a site could use
Origin-Agent-Cluster: ?1;why=parallelism
to indicate that it was mostly interested in origin-keying itself to gain better parallelism with same-site cross-origin pages that it would otherwise share resources with. Or a page could use
Origin-Agent-Cluster: ?1;why=memory-measurement
to indicate that the reason it was origin-keying 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-Agent-Cluster
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-keyed agent clusters from origin policy for now, instead using the Origin-Agent-Cluster
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-Agent-Cluster
, 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-keyed agent clusters 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"],
"origin_keyed_agent_clusters": true
}
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. They would probably use separate headers, however.