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:
-
It introduces a new
cross-origin
value for theCross-Origin-Resource-Policy
HTTP response header, which constitutes an explicit declaration that a given resource may be embedded in cross-origin contexts. -
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 aCross-Origin-Resource-Policy
header which allows the embedding, or pass a CORS check. -
It extends
Cross-Origin-Resource-Policy
to handle some navigation requests in order to deal reasonably withiframe
embeddings andwindow.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
-
Let policy be a new embedder policy.
-
Let parsed item be the result of getting a structured header with "
Cross-Origin-Embedder-Policy
" and "item
". -
If parsed item is neither
failure
nornull
and parsed item’s bare item is "require-corp
":-
Set policy’s value to "
require-corp
". -
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"].
-
-
Set parsed item to the result of getting a structured header with "
Cross-Origin-Embedder-Policy-Report-Only
" and "item
". -
If parsed item is neither
failure
nornull
and parsed item’s bare item is "require-corp
":-
Set policy’s report only value to "
require-corp
". -
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"].
-
-
Return policy.
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:
-
An embedder policy consists of:
-
A string (value) with one of the following values: "
unsafe-none
", "require-corp
", initially "unsafe-none
". -
A string or
null
(reporting endpoint), initiallynull
. -
A string (report only value) with one of the following values: "
unsafe-none
", "require-corp
", initially "unsafe-none
". -
A string or
null
(report only reporting endpoint), initiallynull
.
-
-
The embedder policy is persisted on a number of objects:
-
Document
objects are given an embedder policy property, whose value is an embedder policy. -
WorkerGlobalScope
objects are given a embedder policy property, whose value is an embedder policy. -
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.
- For
-
-
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:-
If creator is not null, set document’s embedder policy to creator embedder policy.
-
-
The initialize the Document object algorithm sets the embedder policy for
Document
s to which a browsing context is navigated by adding a new step directly after Referrer Policy is initialized in step 6:-
Let document’s embedder policy be the result of obtaining an embedder policy from response.
-
-
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:-
Call initialize a global object’s embedder policy from a response given worker global scope and response.
-
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.
-
-
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:
-
If browsingContext is a child browsing context:
-
Let parent be browsingContext’s parent browsing context.
-
Let requestForCORPCheck be a copy of request.
-
Set requestForCORPCheck’s origin to parent’s origin.
-
Set requestForCORPCheck’s client to parent’s active document.
-
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.
-
-
-
The process a navigate response algorthm checks that documents nested in a
require-corp
context themselves positively assertrequire-corp
by adding a new condition to the list in step 1:-
The check a navigation response’s adherence to its embedder’s policy algorithm returns "
Blocked
" when executed upon response and browsingContext.
-
3.1.1. Initializing a global object’s Embedder policy
-
Let policy be a new embedder policy.
-
Let response policy be the result of obtaining an embedder policy from response.
-
If response’s url's scheme is a local scheme:
-
For each of the items in global’s owner set:
-
If the item’s embedder policy's [=embedder policy/value] is "
require-corp
", then set policy’s [=embedder policy/value] to "require-corp
". -
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. -
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
". -
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.
-
-
-
Otherwise:
-
Set policy to response policy.
-
-
Set global’s embedder policy to policy.
3.1.2. Checking a global object’s Embedder policy
-
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).
-
Set blocked url’s username to the empty string, and its password to
null
. -
Set serialized blocked url be the result of executing the URL serializer on blocked url with the exclude fragment flag set.
-
Let body be a new object containing the following properties with keys:
-
key: "
type
", "worker initialization
". -
key: "
blocked-url
", value: serialized blocked url.
-
-
Queue body as "
coep
" for endpoint on settings.
-
If global is a
SharedWorkerGlobalScope
or global is aServiceWorkerGlobalScope
, then return "allowed
". -
Let owner policy be owner’s embedder policy.
-
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. -
If owner policy’s value is "
require-corp
" and child policy’s value is "unsafe-none
":-
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.
-
Return "
blocked
".
-
-
Return "
allowed
".
3.1.3. Process a navigation response
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:
-
Let blocked url be request’s URL.
Note: This is not request’s current URL in order to avoid redirect information leak.
-
Set blocked url’s username to the empty string, and its password to
null
. -
Set serialized blocked url be the result of executing the URL serializer on blocked url with the exclude fragment flag set.
-
Let body be a new object containing the following properties with keys:
-
key: "
type
", "navigation
". -
key: "
blocked-url
", value: serialized blocked url.
-
-
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:
-
Return "
Allowed
" if target is not a child browsing context. -
Let response policy be the result of obtaining an embedder policy from response.
-
Let parent policy be target’s container document's embedder policy.
-
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. -
If parent policy’s value is "
require-corp
" and child policy’s value is "unsafe-none
":-
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.
-
Return "
Blocked
".
-
-
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:
-
The
Cross-Origin-Resource-Policy
grammar is extended to include a "cross-origin
" value. -
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:
-
Return
allowed
if request’s mode is "same-origin
", "cors
", or "websocket
". -
If request’s mode is "
navigate
":-
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.
-
If embedder policy value is "
unsafe-none
", then returnallowed
.
-
-
Let policy be the result of getting
Cross-Origin-Resource-Policy
from response’s header list. -
If policy is
null
and embedder policy value is "require-corp
", then set policy to "same-origin
". -
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
:-
request’s origin's host is same site with request’s current URL's origin's host.
-
request’s origin's scheme is "
https
", or response’s HTTPS state is "none
".
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:
-
Let embedder policy be request’s client's embedder policy.
-
If request’s reserved client is not
null
, then set embedder policy to a new embedder policy. -
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 isblocked
, then run these steps:-
Let blocked url be request’s URL.
-
Set blocked url’s username to the empty string, and its password to
null
. -
Set serialized blocked url be the result of executing the URL serializer on blocked url with the exclude fragment flag set.
-
Let body be a new object containing the following properties with keys:
-
key: "
type
", value: "corp
". -
key: "
blocked-url
", value: serialized blocked url.
-
-
Queue body as "
coep
" for embedder policy’s report only reporting endpoint on request’s client.
-
-
Let result be the result of running cross-origin resource policy internal check with value, request and response.
-
If embedder policy’s reporting endpoint is not
null
and result isblocked
, then run these steps:-
Let blocked url be request’s URL.
-
Set blocked url’s username to the empty string, and its password to
null
. -
Set serialized blocked url be the result of executing the URL serializer on blocked url with the exclude fragment flag set.
-
Let body be a new object containing the following properties with keys:
-
key: "
type
", value: "corp
". -
key: "
blocked-url
", value: serialized blocked url.
-
-
Queue body as "
coep
" for embedder policy’s reporting endpoint on request’s client.
-
-
Return result.
3.3. Integration with Service Worker
In https://w3c.github.io/ServiceWorker/#dom-fetchevent-respondwith, replace 10.1 with the following items.
-
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 isblocked
, 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.