diff --git a/README.md b/README.md index 9df30b0..ea5761f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,37 @@ # Origin Isolation explainer -This proposal provides a way for web applications to achieve **origin isolation**, segregating cross-origin documents into separate agent clusters, and thus allowing browsers to segregate them into different processes. +**Origin isolation** refers to segregating cross-origin documents into separate [agent clusters](https://html.spec.whatwg.org/multipage/webappapis.html#integration-with-the-javascript-agent-cluster-formalism). Translated into developer-observable effects, this means: + +* preventing the [`document.domain`](https://html.spec.whatwg.org/multipage/origin.html#relaxing-the-same-origin-restriction) setter from relaxing the same-origin policy; and +* preventing `SharedArrayBuffer`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. + +This proposal allows web applications to give up these capabilities for their origin, and also hint at why they are doing so, to better guide the browser in its resource allocation decisions. + + + +## Table of contents + +- [Example](#example) +- [Objectives](#objectives) +- [Proposed hints](#proposed-hints) +- [Implementation strategies](#implementation-strategies) +- [How it works](#how-it-works) + - [Background: agent clusters](#background-agent-clusters) + - [Specification plan](#specification-plan) + - [More complicated example](#more-complicated-example) +- [Design choices and alternatives considered](#design-choices-and-alternatives-considered) + - [Origin policy](#origin-policy) + - [The browsing context group scope of isolation](#the-browsing-context-group-scope-of-isolation) + - [Not using feature/document policy](#not-using-featuredocument-policy) + - [Usage hints](#usage-hints) + - [Opt-in, instead of implicit, origin isolation](#opt-in-instead-of-implicit-origin-isolation) +- [Adjacent work](#adjacent-work) + - [`COOP` + `COEP`](#coop--coep) + - [Automatic origin isolation via `COOP` + `COEP`](#automatic-origin-isolation-via-coop--coep) + + ## Example @@ -9,38 +40,38 @@ The delivery mechanism for a web application to achieve origin isolation is [ori ```json { "id": "my-policy", - "origin_isolated": "best-effort" + "isolation": { + "prefer_isolated_event_loop": true, + "prefer_isolated_memory": true + } } ``` and would add a HTTP header ``` -Origin-Policy: allowed=(my-policy) +Origin-Policy: allowed=("my-policy" null) ``` to their responses. -This indicates that any realms (documents or workers) derived from that origin should be separated from other cross-origin realms—even if those realms are [same site](https://github.com/whatwg/url/pull/449). In terms of observable, specified consequences for web developers, this means: +The presence of the `"isolation"` field indicates that any realms (documents or workers) derived from that origin should be separated from other cross-origin realms—even if those realms are [same site](https://html.spec.whatwg.org/multipage/origin.html#same-site). In terms of observable, specified consequences for web developers, this means: * Attempts to set `document.domain` will fail, so cross-origin realms will not be able to synchronously script each other, even if they are same site. -* Attempts to share `SharedArrayBuffer`s to cross-origins via `postMessage()` will fail, even if those realms are same site. +* Attempts to share `SharedArrayBuffer`s to cross-origin realms via `postMessage()` will fail, even if those realms are same site. -In terms of behind-the-scenes implementation, disallowing cross-origin synchronous scripting and shared memory allows implementations to, if they choose, put cross-origin realms in different processes. When implementations do this, web developers gain the following additional advantages: +The two hints provided, `"prefer_isolated_event_loop"` and `"prefer_isolated_memory"`, indicate that the primary reasons the origin is giving up these capabilities is in the hopes that the browser can provide better isolation of the origin's event loop and memory. See below for more details on [the possible hints](#proposed-hints). -* Protection against performance interference from cross-origin iframes or popups. Or, more positively, this allows the browser to spread out work from cross-origin iframes/popups across multiple cores. -* Protection against side channel attacks, such as [Spectre](https://en.wikipedia.org/wiki/Spectre_(security_vulnerability)), even from same-site attackers. -* Memory metrics ([proposal 1](https://github.com/ulan/javascript-agent-memory/blob/master/explainer.md), [proposal 2](https://github.com/WICG/performance-memory/blob/master/explainer.md)) can become origin-focused instead of site-focused, which makes them more useful. - -In other words, this feature allows web developers to voluntarily remove the ability to do certain (rather esoteric) cross-origin operations, in exchange for allowing the browser to create better isolation between origins. +The browser can then use these hints to choose a behind-the-scenes implementation strategy such as isolating the origin into its own process, or its own thread, or memory heap, or any other such implementation construct. These are just hints; ultimately the browser remains in charge of such implementation decisions. ## Objectives Goals: * Allow web applications to isolate themselves from cross-origin memory sharing and synchronous scripting (in a guaranteed way). -* Allow user agents to process-isolate realms 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 (modulo side channels) regardless of implementation process allocation choices. +* 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. +* Allow web applications to provide information to the browser about why they want isolation, in order to better guide the browser's implementation strategies. +* Ensure consistent web-developer-observable behavior regardless of implementation choices (such as process allocation) or the sub-preferences expressed by an origin. (Modulo side channels.) * Allow web applications to easily configure origin isolation for all URLs on their origin. Non-goals: @@ -48,6 +79,30 @@ Non-goals: * Mandate process isolation or other implementation choices for user agents. * Give web developers visibility into process allocation. +## Proposed hints + +The following hints are proposed, all booleans: + +* `"prefer_isolated_event_loop"`: indicates that one reason the origin is opting in to isolation is in the hope that its event loop will be isolated from those of other origins. Origins might want this in order to get more consistent and predictable performance, isolated from whatever is happening in cross-origin iframes or popups. + +* `"prefer_isolated_memory"`: indicates that one reason the origin is opting in to isolation is in the hope of having an as-large-as-possible amount of memory available, such that large allocations from cross-origin pages should not cause the origin's allocations to start failing. Origins that plan on using lots of memory might want this. + +* `"for_side_channel_protection"`: indicates that one reason the origin is opting in to isolation is in the hopes of preventing side-channel attacks such as [Spectre](https://en.wikipedia.org/wiki/Spectre_(security_vulnerability)). Origins might want this if they deal with sensitive data, which would be problematic if exfiltrated. + +* `"for_memory_measurement"`: indicates that one reason the origin is opting in to isolation is in the hopes of enabling accurate memory measurement via the [proposed `performance.measureMemory()` API extensions](https://github.com/ulan/javascript-agent-memory/blob/master/explainer.md#future-api-extensions). This API is proposed to reject if the browser cannot restrict its measurements to only same-origin resources (or cross-origin ones which have opted in via `Cross-Origin-Resource-Policy`). Thus, by indicating that the origin plans to use this API, the browser can take steps to ensure the measurements are restricted. + +If none of these hints are present, i.e. the manifest contains `"isolation": {}` or `"isolation": true`, then it is assumed that the origin wants isolation solely for increased encapsulation, i.e. for the direct effects of preventing `document.domain` usage and `SharedArrayBuffer` memory sharing. + +## Implementation strategies + +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. + +The nuances of this proposal come into play under the following scenarios: + +* When allocating a process-per-origin is infeasible, e.g. because you are operating on a low-memory device or have already allocated a ton of processes. In such scenarios the browser can use the different hints to prioritize how it allocates processes, e.g. prioritizing protection from side-channel attacks above allowing memory measurement. + +* When alternate isolation techniques are available, which give some but not all of the benefits as proccess isolation. For example, Blink is exploring a "multiple Blink threads" project, which would provide isolated event loops, but not side-channel protection. A site which expressed a preference only for isolated event loops could thus be given a new thread, instead of a new process, saving resources. + ## How it works ### Background: agent clusters @@ -78,15 +133,13 @@ In particular, when creating a document or worker, we use the following algorith 1. Otherwise, if an agent cluster keyed by the site in question exists, use that agent cluster. * Example: `https://example.com/` was loaded without origin isolation, and embeds an iframe for `https://example.com/` which also loads without origin isolation. * Example: `https://example.com/` was loaded without origin isolation, but embeds an iframe for `https://example.com/` which loads with origin isolation. In this case the iframe will not be origin-isolated, even though it was requested, because we don't want to separate it from the existing site-keyed documents. -1. If no such agent clusters exist, and the origin policy contains an `"origin_isolated"` member not set to `"none"`, then create a new agent cluster keyed by origin, and use it. +1. If no such agent clusters exist, and the origin policy contains an `"isolation"` member, then create a new agent cluster keyed by origin, and use it. * Example: a new tab is created which loads `https://example.com/`, whose origin policy specifies origin isolation. 1. Otherwise, create a new agent cluster keyed by site. * Example: a new tab is created which loads `https://example.com/`, whose origin policy does not specify origin isolation. This will automatically cause `SharedArrayBuffer`s to no longer be shareable cross-origin, because of the agent cluster check in [StructuredDeserialize](https://html.spec.whatwg.org/multipage/structured-data.html#structureddeserialize). We'd also need to add a check to the [`document.domain` setter](https://html.spec.whatwg.org/multipage/origin.html#dom-document-domain) to make it throw. (Ideally we'd find some way of restructuring the existing checks in `document.domain` so that they are also tied to agent cluster boundaries.) -The criteria of looking for `"origin_isolated"` not set to `"none"`, instead of specifically looking for `"best-effort"`, is done to increase future compatibility. See more on this [below](#the-best-effort-string). In the meantime, user agents should warn for any unknown values, explaining to the web developer that the unknown value is being treated as `"best-effort"`. - ### More complicated example Consider the following scenario: @@ -138,21 +191,17 @@ The downside of the per-user agent model is that it makes it very hard for web a ### Not using feature/document policy -Given the choice of origin policy to deliver this, why did we choose a new top-level key (`"origin_isolated"`) instead of using [feature policy](https://github.com/w3c/webappsec-feature-policy) or [document policy](https://github.com/w3c/webappsec-feature-policy/blob/master/document-policy-explainer.md)? Both of those are envisioned to be configurable on an origin-wide basis using origin policy, after all. +Given the choice of origin policy to deliver this, why did we choose a new top-level key (`"isolation"`) instead of using [feature policy](https://github.com/w3c/webappsec-feature-policy) or [document policy](https://github.com/w3c/webappsec-feature-policy/blob/master/document-policy-explainer.md)? Both of those are envisioned to be configurable on an origin-wide basis using origin policy, after all. 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. -### String-based enumeration +### Usage hints -We originally considered `"origin_isolated": true` for configuring origin isolation. However, a string-based enumeration is more extensible in case we want to add more modes in the future. Some ideas that have been thrown around: +We've considered several previous designs for origin isolation, such as just `"isolated": true` to indicate isolation with no additional information, or something like `"isolation_implementation_preference": "process"` / `"thread"` / `"separate-heap"` to directly indicate the preferred implementation strategy. See some discussion in [#1](https://github.com/domenic/origin-isolation/issues/1). -* `"required"` / `"for-security"` / `"process-isolated"` / `"no-spectres"`, which will fail to load documents on that origin if the user agent cannot guarantee process isolation. This would be used for sites that demand protection from side channel attacks like Spectre. +A simple boolean gives the user agent too little information to make the right choices. Faced with multiple possible implementation strategies and constrained resources, how can it best decide what strategy to use? Currently Chrome uses heuristics, like "a page containing a password input probably needs side-channel protection". Making this more explicit can only benefit pages. In the end, user agents which don't find the extra information useful can ignore it. -* `"for-performance"` / `"event-loop-isolated"`, which would tell the user agent that the main goal of isolation is performance isolation, and thus it could consider or prefer different strategies like using separate threads instead of separate processes. - -* `"non-scriptable"`, which would tell the user agent that no degree of performance or process isolation is required, but the web application still wants to prevent synchronous cross-realm scripting. This could be useful to increase encapsulation. - -The proposal's processing model is designed to be forward-compatible in the sense that if user agents add new values like these in the future, and web applications start using them, user agents that don't support the new values will instead treat them as `"best-effort"`. This is better than the alternative, of treating unknown values as `"none"`, which would fail to achieve any origin isolation in those user agents. +On the other end of the spectrum, directly indicating implementation choices to the browser is overstepping a bit. It's unlikely a site author has enough information to confidently declare that they need a process; instead, they know things about their performance, memory, or security needs, which the browser can take into account. ### Opt-in, instead of implicit, origin isolation @@ -163,4 +212,28 @@ Another potential road we explored was to try to find scenarios where a process- 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 here. 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. +In addition to the same concerns as stated above about how this doesn't give useful hints to the user agent, 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. + +## Adjacent work + +### `COOP` + `COEP` + +The [`Cross-Origin-Opener-Policy`](https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e) and [`Cross-Origin-Embedder-Policy`](https://mikewest.github.io/corpp/) headers, when combined correctly, can be used to create a "cross-origin isolated" agent cluster: that is, an agent cluster which contains only same-origin content, or content which has (via [`Cross-Origin-Resource-Policy`](https://fetch.spec.whatwg.org/#cross-origin-resource-policy-header)) opted in to being treated as same-origin with the embedding agent cluster. + +Origin isolation is complementary to the type of "cross-origin isolation" provided by `COOP` + `COEP`. In particular, `COOP` cuts off the opener relations for same-site cross-origin popups, isolating them from their opener. But `COEP` does not cut off the relationship with nested browsing contexts (e.g. iframes); it just ensures that they are OK being embedded. Thus, under a `COOP` + `COEP` world, `document.domain` still allows synchronous scripting to cross-origin same-site iframes, and `postMessage()` can still send `SharedArrayBuffer`s to such destinations. Whereas origin isolation cuts goes further and cuts off that possibility. + +Stated in spec terms, `COOP` + `COEP` cross-origin isolated agent clusters are still site-keyed (although any cross-origin popups will not be placed in them, so with regard to popups, they are effectively origin-keyed). Whereas origin isolation changes the agent cluster to be origin-keyed. + +However, see the next section... + +### Automatic origin isolation via `COOP` + `COEP` + +A recent idea, raised in [whatwg/html#5122](https://github.com/whatwg/html/issues/5122), is to have `COOP` + `COEP` automatically turn on 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 `SharedArrayBuffer`. 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: + +* Turning on `COOP` + `COEP` does not provide a clear signal that the origin desires origin isolation, or why it would desire origin isolation. As discussed at length above, these signals can be useful for the browser in making decisions on implementation strategies. For example, consider a site with 30 iframes containing ads, which wishes to achieve side-channel protection. With an explicit dedicated isolation signal, the top-level site could explain its desire for side-channel protection, and recieve a dedicated process, while letting the ad frames get coalesced into a single process. Whereas if `COOP` + `COEP` by itself automatically created a process-isolation signal, then the browser could only use heuristics to guess whether to allocate 2 processes, or 31 processes, or something in between. + +* Deploying `COOP` + `COEP` could be a significant burden for sites, e.g. in terms of how it requires all of their embedded content to be updated with `CORP` headers. Some sites may not want to use any of the features enabled by `COOP` + `COEP` (e.g. `SharedArrayBuffer` or `process.measureMemory()`), and so be unmotivated to take on this burden. But they still might want origin isolation, e.g. of the event loop or heap isolation variety. In such cases, this origin-policy based approach would be much easier to deploy than `COOP` + `COEP`, since it does not require any embeddee cooperation.