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:
mikewest/corpp

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-site value for the Cross-Origin-Resource-Policy HTTP response header, which constitutes an explicit declaration that a given resource may be embedded in cross-site 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-token

The only currently valid Cross-Origin-Embedder-Policy value is "require-corp".

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. Parsing

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

  2. Let header be the result of getting Cross-Origin-Embedder-Policy from response’s header list.

  3. If header is not null:

    1. Set header to the result of isomorphic decoding header.

    2. Let parsed policy be the result of executing the Structured Header parsing algorithm with input_string set to header, and header_type set to "item".

      If parsing fails, set parsed policy to "none".

      The ASCII requirements of Structured Headers are somewhat unclear to me. Do we need to check whether header is an ASCII string before passing it into the parsing algorithm? <https://github.com/httpwg/http-extensions/issues/662>

    3. If parsed policy is "require-corp", set policy to "require-corp".

  4. Return policy.

Note: This fails open (by defaulting to "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 none
require-corp require-corp
unknown-value none
require-corp, unknown-value none
unknown-value, unknown-value none
unknown-value, require-corp none
require-corp, require-corp 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 is a string with one of the following values: "none", "require-corp".

  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 defaulting to "none".

    2. WorkerGlobalScope objects are given a embedder policy property, whose value is an embedder policy defaulting to "none".

    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 a new step 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.

  6. 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 "none".

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

  3. Run the steps corresponding to the first matching statement:

    response’s url's scheme is a local scheme
    global is a DedicatedWorkerGlobalScope:
    1. For each of the items in global’s owner set:

      1. If the item’s embedder policy is "require-corp", set policy to "require-corp".

    global is a SharedWorkerGlobalScope:
    global is a ServiceWorkerGlobalScope:
    1. Set policy to response policy.

  4. Set global’s embedder policy to policy.

3.1.2. 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 check a navigation response’s adherence to its embedder’s policy given 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 any of the following statements are true:

  2. Return "Blocked".

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-site" 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. The cross-origin resource policy check needs to be performed _after_ the relevant service worker has the opportunity to respond to a request, as it may otherwise be allowed to respond to a require-corp client with an opaque response which doesn’t assert CORP.

3.2.1. Cross-Origin Resource Policy Checks

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

  1. Let embedder policy be "require-corp".

  2. Set embedder policy to "none" if both of the following statements are true:

  3. Return allowed if any of the following statements are true:

    • request’s mode is "same-origin", "cors", or "websocket".

    • request’s mode is "navigate", and embedder policy is "none".

  4. ASSERT: request’s mode is "no-cors" or "navigate". If request’s mode is "navigate", embedder policy is "require-corp".

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

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

  7. 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.

    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.

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.github.io/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. 2 November 2017. WD. 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

The ASCII requirements of Structured Headers are somewhat unclear to me. Do we need to check whether header is an ASCII string before passing it into the parsing algorithm? <https://github.com/httpwg/http-extensions/issues/662>
The cross-origin resource policy check needs to be performed _after_ the relevant service worker has the opportunity to respond to a request, as it may otherwise be allowed to respond to a require-corp client with an opaque response which doesn’t assert CORP.
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.