Cross-Origin Embedder Policy

A Collection of Interesting Ideas,

Issue Tracking:
whatwg/html topic: cross-origin-embedder-policy
Inline In Spec
Editor:
(Google Inc.)
Version History:
WICG/cross-origin-embedder-policy

Abstract

In order to support interesting and powerful APIs in a post-Spectre world, it seems necessary to ensure that resoures are only voluntarily embedded into a potentially-dangerous context. This document sketches out a potential opt-in mechanism which relies upon explicit declaration of a `Cross-Origin-Resource-Policy` for each embedded resource, defined as a series of monkey-patches against HTML and Fetch which are intended to be upstreamed.

1. Introduction

The same-origin policy’s restrictions against direct access to another origin’s resources is, unfortunately, insufficient in the face of speculative execution attacks like [spectre]. Merely _loading_ another origins' resource may be enough to bring its content into a memory space which may be probed by an attacker, even if the browser would otherwise prevent access to the resource through explicit channels.

Given this context, user agents are rethinking the threat model under which they operate (e.g. [chromium-post-spectre-rethink]). It would be unfortunate indeed to prevent the web platform from legitimately using APIs like SharedArrayBuffer that accidentally improve attackers' ability to exploit speculation attacks, but at the same time, many user agents have agreed that it seems unreasonable to enable those APIs without additional mitigation.

The approach sketched out in this document tackles one piece of the broader problem by giving developers the ability to require an explicit opt-in from any resource which would be embedded in a given context. User agents can make that requirement a precondition for some APIs that might otherwise leak data about cross-origin resources, which goes some way towards ensuring that any leakage is voluntary, not accidental.

To that end, this proposal does three things:

  1. It introduces a new cross-origin value for the Cross-Origin-Resource-Policy HTTP response header, which constitutes an explicit declaration that a given resource may be embedded in cross-origin contexts.

  2. It introduces a new Cross-Origin-Embedder-Policy header which shifts the default behavior for resources loaded in a given context to an opt-in model, in which cross-origin responses must either assert a Cross-Origin-Resource-Policy header which allows the embedding, or pass a CORS check.

  3. It extends Cross-Origin-Resource-Policy to handle some navigation requests in order to deal reasonably with iframe embeddings and window.open().

Together, these would allow a user agent to gate access to interesting APIs (like the aforementioned SharedArrayBuffer) on a top-level context opting-into Cross-Origin-Embedder-Policy, which in turn gives servers the ability to inspect incoming requests and make reasonable decisions about when to allow an embedding.

The rest of this document monkey-patches [HTML] and [Fetch] in order to document the details of the bits and pieces discussed above.

2. Framework

2.1. The Cross-Origin-Embedder-Policy HTTP Response Header

The Cross-Origin-Embedder-Policy HTTP response header field allows a server to declare an embedder policy for a given document. It is a Structured Header whose value MUST be a token. [I-D.ietf-httpbis-header-structure] Its ABNF is:

Cross-Origin-Embedder-Policy = sh-item

The Cross-Origin-Embedder-Policy value consists of one token ("require-corp") which may have a parameter specifying a string which represents the endpoint for violation reporting.

In order to support forward-compatibility with as-yet-unknown request types, user agents MUST ignore this header if it contains an invalid value. Likewise, user agents MUST ignore this header if the value cannot be parsed as a sh-token.

2.2. The Cross-Origin-Embedder-Policy-Report-Only HTTP Response Header

The Cross-Origin-Embedder-Policy-Report-Only HTTP response header field allows a server to declare an embedder policy for a given document. It is a Structured Header whose value MUST be a token. [I-D.ietf-httpbis-header-structure] Its ABNF is:

Cross-Origin-Embedder-Policy-Report-Only = sh-item

The Cross-Origin-Embedder-Policy-Report-Policy value consists of one token ("require-corp") which may have a parameter specifying a string which represents the endpoint for violation reporting.

The Cross-Origin-Embedder-Policy-Report-Policy value is used only when there is no Cross-Origin-Embedder-Policy header present.

In order to support forward-compatibility with as-yet-unknown request types, user agents MUST ignore this header if it contains an invalid value. Likewise, user agents MUST ignore this header if the value cannot be parsed as a sh-token.

2.3. Parsing

To obtain a response’s embedder policy given a response (response):
  1. Let policy be a new embedder policy.

  2. Let parsed item be the result of getting a structured header with "Cross-Origin-Embedder-Policy" and "item".

  3. If parsed item is neither failure nor null and parsed item’s bare item is "require-corp":

    1. Set policy’s value to "require-corp".

    2. If parsed item’s parameters["report-to"] exists and it is a string, then set policy’s reporting endpoint to parsed item’s parameters["report-to"].

  4. Set parsed item to the result of getting a structured header with "Cross-Origin-Embedder-Policy-Report-Only" and "item".

  5. If parsed item is neither failure nor null and parsed item’s bare item is "require-corp":

    1. Set policy’s report only value to "require-corp".

    2. If parsed item’s parameters["report-to"] exists and it is a string, then set policy’s report only reporting endpoint to parsed item’s parameters["report-to"].

  6. Return policy.

Note: This fails open (by defaulting to "unsafe-none") in the presence of a header that cannot be parsed as a token. This includes inadvertant lists created by combining multiple instances of the Cross-Origin-Embedder-Policy header present in a given response:
Cross-Origin-Embedder-Policy Final Policy
No header delivered unsafe-none
require-corp require-corp
unknown-value unsafe-none
require-corp, unknown-value unsafe-none
unknown-value, unknown-value unsafe-none
unknown-value, require-corp unsafe-none
require-corp, require-corp unsafe-none

3. Integrations

3.1. Integration with HTML

When creating a document, user agents will process Cross-Origin-Embedder-Policy headers delivered by the server, imposing any restrictions it asserts. Likewise, user agents MUST also take into account the embedder policy asserted by the document’s opener or embedder, ensuring that they’re properly imposed as well. To do so, HTML is patched as follows:

  1. An embedder policy consists of:

    1. A string (value) with one of the following values: "unsafe-none", "require-corp", initially "unsafe-none".

    2. A string or null (reporting endpoint), initially null.

    3. A string (report only value) with one of the following values: "unsafe-none", "require-corp", initially "unsafe-none".

    4. A string or null (report only reporting endpoint), initially null.

  2. The embedder policy is persisted on a number of objects:

    1. Document objects are given an embedder policy property, whose value is an embedder policy.

    2. WorkerGlobalScope objects are given a embedder policy property, whose value is an embedder policy.

    3. Environment settings objects are given a embedder policy accessor, which has the following implementations:

      For Window objects:

      Return the embedder policy of window’s associated Document.

      For WorkerGlobalScope objects:

      Return worker global scope’s embedder policy.

  3. The create a new browsing context algorithm sets the embedder policy for a browsing context’s initial about:blank document by adding a new step directly after Referrer Policy is initialized in step 11 of the existing algorithm which will copy any creator document’s policy:

    1. If creator is not null, set document’s embedder policy to creator embedder policy.

  4. The initialize the Document object algorithm sets the embedder policy for Documents to which a browsing context is navigated by adding a new step directly after Referrer Policy is initialized in step 6:

    1. Let document’s embedder policy be the result of obtaining an embedder policy from response.

  5. The run a worker algorithm sets the embedder policy for WorkerGlobalScope objects by adding new steps directly after Referrer Policy is initialized in step 12.5:

    1. Call initialize a global object’s embedder policy from a response given worker global scope and response.

    2. If the result of check a global object’s embedder policy given worker global scope, owner and request is "blocked", then set response to a network error.

  6. The process a navigate fetch algorithm runs the cross-origin resource policy check for navigation for nested browsing contexts by adding the following step before step 6:

    1. If browsingContext is a child browsing context:

      1. Let parent be browsingContext’s parent browsing context.

      2. Let requestForCORPCheck be a copy of request.

      3. Set requestForCORPCheck’s origin to parent’s origin.

      4. Set requestForCORPCheck’s client to parent’s active document.

      5. If the result of cross-origin resource policy check with requestForCORPCheck and response is blocked, then set response to a network error.

        Note: Here we’re running the cross-origin resource policy check against the parent browsing context rather than sourceBrowsingContext. This is because we do care about the same-originness of the embedded content against the parent context, not the navigation source.

  7. The process a navigate response algorthm checks that documents nested in a require-corp context themselves positively assert require-corp by adding a new condition to the list in step 1:

3.1.1. Initializing a global object’s Embedder policy

To initialize a global object’s embedder policy from a response, given a global object (global) and a response (response):
  1. Let policy be a new embedder policy.

  2. Let response policy be the result of obtaining an embedder policy from response.

  3. If response’s url's scheme is a local scheme:

    1. For each of the items in global’s owner set:

      1. If the item’s embedder policy's [=embedder policy/value] is "require-corp", then set policy’s [=embedder policy/value] to "require-corp".

      2. If policy’s [=embedder policy/reporting endpoint] is null and the item’s reporting endpoint is non-null, then policy’s reporting endpoint to the item’s reporting endpoint.

      3. If the item’s embedder policy's [=embedder policy/report only value] is "require-corp", then set policy’s [=embedder policy/value] to "require-corp".

      4. If policy’s [=embedder policy/report only reporting endpoint] is null and the item’s report only reporting endpoint is non-null, then policy’s report only reporting endpoint to the item’s report only reporting endpoint.

  4. Otherwise:

    1. Set policy to response policy.

  5. Set global’s embedder policy to policy.

3.1.2. Checking a global object’s Embedder policy

To Queue a Cross-Origin Embedder Policy violation on worker initialization given a request (request), a string (endpoint) and an environment settings object (settings), run the following steps:
  1. Let blocked url be request’s URL.

    Note: This is not request’s current URL in order to avoid leaking information about redirect targets (see https://w3c.github.io/webappsec-csp/#security-violation-reports).

  2. Set blocked url’s username to the empty string, and its password to null.

  3. Set serialized blocked url be the result of executing the URL serializer on blocked url with the exclude fragment flag set.

  4. Let body be a new object containing the following properties with keys:

    • key: "type", "worker initialization".

    • key: "blocked-url", value: serialized blocked url.

  5. Queue body as "coep" for endpoint on settings.

To check a global object’s embedder policy, given a global object (global), a environment settings object (owner) and a request (request), then
  1. If global is a SharedWorkerGlobalScope or global is a ServiceWorkerGlobalScope, then return "allowed".

  2. Let owner policy be owner’s embedder policy.

  3. If owner policy’s report only value is "require-corp" and owner policy’s report only reporting endpoint is not null and response policy’s value is "unsafe-none", then queue a Cross-Origin Embedder Policy vioalation on worker initialization with request, owner policy’s report only reporting endpoint and owner.

  4. If owner policy’s value is "require-corp" and child policy’s value is "unsafe-none":

    1. If owner policy’s reporting endpoint is not null, then queue a Cross-Origin Embedder Policy vioalation on worker initialization with request, owner policy’s reporting endpoint and owner.

    2. Return "blocked".

  5. Return "allowed".

3.1.3. Process a navigation response

If a document’s embedder policy is "require-corp", then any document it embeds in a nested browsing context must positively assert a "require-corp" embedder policy (see § 4.3 Cascading vs. requiring embedder policies).

To Queue a Cross-Origin Embedder Policy violation on navigation given a request (request), a string (endpoint) and an environment settings object (settings), run the following steps:

  1. Let blocked url be request’s URL.

    Note: This is not request’s current URL in order to avoid redirect information leak.

  2. Set blocked url’s username to the empty string, and its password to null.

  3. Set serialized blocked url be the result of executing the URL serializer on blocked url with the exclude fragment flag set.

  4. Let body be a new object containing the following properties with keys:

    • key: "type", "navigation".

    • key: "blocked-url", value: serialized blocked url.

  5. Queue body as "coep-navigation" for endpoint on settings.

To check a navigation response’s adherence to its embedder’s policy given a request (request), a response (response), and a target browsing context (target), execute the following steps, which will return "Allowed" or "Blocked" as appropriate:

  1. Return "Allowed" if target is not a child browsing context.

  2. Let response policy be the result of obtaining an embedder policy from response.

  3. Let parent policy be target’s container document's embedder policy.

  4. If parent policy’s report only value is "require-corp" and parent policy’s report only reporting endpoint is not null and response policy’s value is "unsafe-none", then queue a Cross-Origin Embedder Policy vioalation on navigation with request, parent policy’s report only reporting endpoint and target’s container document.

  5. If parent policy’s value is "require-corp" and child policy’s value is "unsafe-none":

    1. If parent policy’s reporting endpoint is not null, then queue a Cross-Origin Embedder Policy vioalation on navigation with request, parent policy’s reporting endpoint and target’s container document.

    2. Return "Blocked".

  6. Return "Allowed".

3.2. Integration with Fetch

When fetching resources, user agents should examine both the request's client and reserved client to determine the applicable embedder policy, and apply any constraints that policy expresses to incoming responses. To do so, Fetch is patched as follows:

  1. The Cross-Origin-Resource-Policy grammar is extended to include a "cross-origin" value.

  2. The cross-origin resource policy check is rewritten to take the embedder policy into account, and to cover some navigation requests in addition to no-cors requests.

3.2.1. Cross-Origin Resource Policy Checks

To perform a cross-origin resource policy internal check given a string (embedder policy value), a request (request) and a response (response), run these steps:

  1. Return allowed if request’s mode is "same-origin", "cors", or "websocket".

  2. If request’s mode is "navigate":

    1. ASSERT: This algorithm will only be called when request targets a nested browsing context; therefore, its destination is either "frame", "iframe", "embed", or "object".

      Note: This relies on whatwg/fetch/#948.

    2. If embedder policy value is "unsafe-none", then return allowed.

  3. Let policy be the result of getting Cross-Origin-Resource-Policy from response’s header list.

  4. If policy is null and embedder policy value is "require-corp", then set policy to "same-origin".

  5. Switch on policy and run the associated steps:

    null
    cross-origin

    Return allowed.

    same-origin

    If request’s origin is same origin with request’s current URL's origin, then return allowed.

    Otherwise, return blocked.

    same-site

    If both of the following statements are true, then return allowed:

    Otherwise, return blocked.

    Note: Cross-Origin-Resource-Policy: same-site does not consider a response delivered via a secure transport to match a non-secure requesting origin, even if their hosts are otherwise same site. Securely-transported responses will only match a securely-transported initiator.

    Otherwise

    Return allowed.

    Anne suggested that we ought to fail closed instead in the presence of COEP in a comment on the relevant PR. That seems reasonable to me, if we can get some changes into CORP along the lines of whatwg/fetch#760, as they seem like useful extensions, and I think it’ll be more difficult to ship them after inverting the error-handling behavior.

To perform a cross-origin resource policy check given a request (request) and a response (response), run these steps:

  1. Let embedder policy be request’s client's embedder policy.

  2. If request’s reserved client is not null, then set embedder policy to a new embedder policy.

  3. If embedder policy’s report only reporting endpoint is not null and the result of running [$cross-origin resource policy internal check] with report only value, request and response is blocked, then run these steps:

    1. Let blocked url be request’s URL.

    2. Set blocked url’s username to the empty string, and its password to null.

    3. Set serialized blocked url be the result of executing the URL serializer on blocked url with the exclude fragment flag set.

    4. Let body be a new object containing the following properties with keys:

      • key: "type", value: "corp".

      • key: "blocked-url", value: serialized blocked url.

    5. Queue body as "coep" for embedder policy’s report only reporting endpoint on request’s client.

  4. Let result be the result of running cross-origin resource policy internal check with value, request and response.

  5. If embedder policy’s reporting endpoint is not null and result is blocked, then run these steps:

    1. Let blocked url be request’s URL.

    2. Set blocked url’s username to the empty string, and its password to null.

    3. Set serialized blocked url be the result of executing the URL serializer on blocked url with the exclude fragment flag set.

    4. Let body be a new object containing the following properties with keys:

      • key: "type", value: "corp".

      • key: "blocked-url", value: serialized blocked url.

    5. Queue body as "coep" for embedder policy’s reporting endpoint on request’s client.

  6. Return result.

3.3. Integration with Service Worker

In https://w3c.github.io/ServiceWorker/#dom-fetchevent-respondwith, replace 10.1 with the following items.

  1. If response is not a Response object, or _event_’s request’s associated request’s mode is "no-cors" and the result of performing a cross-origin resource policy check with _event_’s request’s associated request and _response_’s associated response is blocked, then set the respond-with-error flag.

Also add the following note.

The cross-origin resource policy check performed here ensures that a Service Worker cannot respond to a client that requires CORP with an opaque response that doesn’t assert CORP.

4. Implementation Considerations

4.1. Why not require CORS instead?

An earlier version of this propsal leaned on CORS rather than CORP. Why didn’t we run with that model instead?

This proposal posits that there’s a meaningful distinction between a server’s assertions that "You, vague acquaintance, may embed me." and "You, dearest friend, may read me." Cross-Origin-Resource-Policy grants no explicit access to a resources' content, unlike CORS, and seems like it’s just good-enough to support the explicit declaration of embeddableness that this proposal requires. CORS goes further, and especially in the short-term it seems that there’s real risk in developers blindly enabling CORS in order to meet the embedding requirements we want to impose here, opening themselves up to direct attack in the process.

That is, it seems likely that some subset of developers would implement a CORS requirement in the simplest way possible, by reflecting the Origin header in an Access-Control-Allow-Origin header. If these resources contain interesting data about users (as advertisements, for example, are wont to do), then it’s possible that data will end up being more widely available than expected.

CORP does not create the same risk. It seems strictly lower-privilege than CORS, and a reasonable place for us to start.

4.2. Forward-compatibility

The header defined in this document is small and single-purpose, which is a real advantage for comprehensibility. I wonder, however, if an extensible alternative would be reasonable. For example, if we’re serious about moving to credentialless requests, it would be annoying to do so by defining yet another header. Perhaps something more generic that accepts a dictionary rather than a single token? That is:

Embedee-Policy: opt-in=required, credentials=cors-only

Perhaps it will be possible to do everything we want by defining a new tokens, but I worry a bit that we’ll follow [Referrer-Policy] into some pretty convoluted token names if we go that route. Splitting out the axes along which we’d like to make decisions seems like it might be a good strategy to consider.

4.3. Cascading vs. requiring embedder policies

An earlier version of this proposal called for a nested document’s embedder policy to be inherited from its parent. This would ensure that a document that asserted require-corp would require its framed children to do the same.

We decided that this is the wrong model to start with. Instead, we now require the framed document itself to assert Cross-Origin-Embedder-Policy: require-corp, and block the load if it doesn’t. That seems safer, insofar as it would give the embedder less control over the embedee’s state. It also ensures that the embedee’s developer would always see consistent behavior in the given document no matter whether its loaded as a frame or as a top-level document.

This might be a requirement we can relax in the future, as it does have potential implications for eventual deployment. It makes sense to begin with the requirement, however, as loosening constraints is significantly simpler than imposing new constraints in the future.

Conformance

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript Language Specification. URL: https://tc39.es/ecma262/
[Fetch]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[I-D.ietf-httpbis-header-structure]
Mark Nottingham; Poul-Henning Kamp. Structured Headers for HTTP. ID. URL: https://tools.ietf.org/html/draft-ietf-httpbis-header-structure
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[SERVICE-WORKERS-1]
Alex Russell; et al. Service Workers 1. 19 November 2019. CR. URL: https://www.w3.org/TR/service-workers-1/
[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/

Informative References

[CHROMIUM-POST-SPECTRE-RETHINK]
The Chromium Project. Post-Spectre Threat Model Re-Think. URL: https://chromium.googlesource.com/chromium/src/+/master/docs/security/side-channel-threat-model.md
[Referrer-Policy]
Jochen Eisinger; Emily Stark. Referrer Policy. 26 January 2017. CR. URL: https://www.w3.org/TR/referrer-policy/
[SPECTRE]
Paul Kocher; et al. Spectre Attacks: Exploiting Speculative Execution. URL: https://spectreattack.com/spectre.pdf

Issues Index

Anne suggested that we ought to fail closed instead in the presence of COEP in a comment on the relevant PR. That seems reasonable to me, if we can get some changes into CORP along the lines of whatwg/fetch#760, as they seem like useful extensions, and I think it’ll be more difficult to ship them after inverting the error-handling behavior.