1. A problem
Sites that wish to continue using SharedArrayBuffer must opt-into cross-origin isolation. Among other things, cross-origin isolation will block the use of cross-origin resources and documents unless those resources opt-into inclusion via either CORS or CORP. This behavior ships today in Firefox, and Chrome aims to ship it as well in 2021H1.
The opt-in requirement is generally positive, as it ensures that developers have the opportunity to adequately evaluate the rewards of being included cross-site against the risks of potential data leakage via those environments. It poses adoption challenges, however, as it does require developers to adjust their servers to send an explicit opt-in. This is challenging in cases where there’s not a single developer involved, but many. Google Earth, for example, includes user-generated content in sandboxed frames, and it seems somewhat unlikely that they’ll be able to ensure that all the resources typed in by all their users over the years will do the work to opt-into being loadable.
Cases like Earth are, likely, outliers. Still, it seems clear that adoption of any opt-in mechanism is going to be limited. From a deployment perspective (especially with an eye towards changing default behaviors), it would be ideal if we could find an approach that provided robust-enough protection against accidental cross-process leakage without requiring an explicit opt-in.
2. A proposal
The goal of the existing opt-in is to block interesting data that an attacker
wouldn’t otherwise have access to from flowing into a process they control. It
might be possible to obtain a similar result by minimizing the risk that
outgoing requests will generate responses personalized to a specific user by
extending coep to
support a new credentialless
mode which strips credentials (cookies, client
certs, etc) by default for no-cors subresource requests. Let’s explore that
addition first, then look at whether it’s Good Enough to enable cross-origin
isolation.
2.1. Subresource requests
In this new COEP variant, cross-origin no-cors subresource requests would be sent without credentials. Specific requests which require credentials can opt-into including them, at the cost of shifting the request’s mode to require a CORS check on the response. This bifurcation between credentiallessness and CORS means either that servers don’t have browser-provided identifiers which could be used to personalize a response (see the isolation section below), or that they explicitly opt-in to exposing the response’s content to the requesting origin.
As an example, consider a developer who wishes to load an image into a context
isolated in the way described above. The <img>
element has a crossorigin
attribute which allows developers to alter the outgoing request’s state. In this
new mode, the following table describes the outgoing request’s properties in
Fetch’s terms for various values:
Resource | Request’s Mode | Request’s Credentials Mode | includeCredentials COEP:unsafe-none | includeCredentials COEP:credentialless |
<img src="https://same-origin/">
| same-origin
| include
| true
| true
|
<img src="https://cross-origin/">
| no-cors
| include
| true
| false
|
<img src="https://cross-origin/" crossorigin="anonymous">
| no-cors
| omit
| false
| false
|
<img src="https://cross-origin/" crossorigin="use-credentials">
| cors
| include
| true
| true
|
2.1.1. redirect
The decision to include credentials is done indepently for each request. The variable includeCredentials is set for the initial request, but also after each redirect.
For example, credentials are not included for a cross-origin no-cors request, but they can be added in the next request if it redirects to a same-origin resource.
2.2. Main resource requests
Cross-origin nested navigational requests (<iframe>
, etc) are more
complicated, as they present risks different in kind from subresources. Frames
create a browsing context with an origin distinct from the parent, which has
implications on the data it has access to via requests on the one hand and
storage APIs on the other. Given this capability, it seems clear that we can’t
just strip credentials from the nested navigational request and call it a day in
the same way that we could with subresources.
For this reason, COEP:credentialless
must be as strict as COEP:require-corp
for navigational requests. It works identically.
That is to say:
-
If the parent sets
COEP:credentialless
orCOEP:require-corp
, then the children must also use one of those headers. The two COEP values can be used and mixed in any order. If the children usesCOEP:unsafe-none
, its response is blocked. -
If the parent sets
COEP:credentialless
orCOEP:require-corp
, then the children is required to specify a CORP header when the response is cross-origin.
Note: To help developers with embedding cross-origin <iframe>
without
opt-in from the embeddee, the anonymous
iframe project has been
proposed. It is orthogonal to COEP:credentialless
, which only affects
subresources.
2.3. CacheStorage requests
See the issue: w3c/ServiceWorker/issues/1592
With CacheStorage’s put() and match() methods, a response fetched from a COEP:unsafe-none
context can be retrieved from a COEP:credentialless
or COEP:require-corp
context.
Similarly to COEP:require-corp
, the behavior of CacheStorage must be specified
for COEP:credentialless
.
The solution proposed is to store the includecredentials
variable from the http-network-or-cache-fetch algorithm into the response. Then during the corp
check,
to require CORP for responses requested with credentials.
2.4. Cross-origin Isolation
Above, we asserted that the core goal of the existing opt-in requirement is to
block interesting data that an attacker wouldn’t otherwise have access to from
flowing into a process they control. Removing credentials from outgoing requests
seems like quite a reasonable way to deal with this for the kinds of requests
which may vary based on browser-mediated credentials (cookies, client certs,
etc). In these cases, COEP:credentialless
would seem to substantially mitigate
the risk of personalized data flowing into an attacker’s process.
Some servers, however, don’t actually use browser-mediated credentials to control access to a resource. They may examine the network characteristics of a user’s request (originating IP address, relationship with the telco, etc) in order to determine whether and how to respond; or they might not even be accessible to attackers directly, instead requiring a user to be in a privileged network position. These resources would continue to leak data in a credentialless model.
Let’s assert for the moment that servers accessible only via a privileged network position can be dealt with entirely by putting a wall between "public" and "private", along the lines of the [private-network-access]. Successfully rolling out that kind of model would address the threat of this kind of leakage. As such [private-network-access] is a dependency of COEP:credentialless.
IP-based authentication models are, on the other hand, more difficult to address. Though the practice is unfortunate in itself (users should have control over their state vis a vis servers they interact with on the one hand, and sensitive data should assume a zero-trust network on the other), we know it’s used in the wild for things like telco billing pages. In a credentialless isolation model, resources these servers expose would continue to flow into cross-origin processes unless and until they explicitly opted-out of that inclusion via CORP. We can minimize the risk of these attacks by increasing CORB’s robustness on the one hand, and requiring opt-in for embedded usage on the other.
This leaves us with a trade-off to evaluate: COEP:credentialless
seems
substantially easier than COEP:require-corp
to deploy, both as an opt-in in
the short-term, and (critically) as default behavior in the long term. It does
substantially reduce the status quo risk. At the same time, it doesn’t prevent a
category of resources from flowing into attackers' processes. We have reasonable
ideas about one chunk of these resources, and would simply not protect the other
without explicit opt-in.
Perhaps that’s a trade-off worth taking? The mechanism seems worth defining regardless, even if we don’t end up considering it a fully cross-origin isolated context.
The rest of this document monkey-patches [HTML], [Fetch] in order to document the details of the bits and pieces discussed above.
3. Integration with HTML
Note: This corresponds to the following HTML specification change: whatwg/html/pull/6638.
3.1. Embedder policy value
In the embedder-policy-value section, add the credentialless
value:
An embedder policy value controls the fetching of cross-origin resources without explicit permission from resource owners. There are three such values:
- "
unsafe-none
" -
This is the default value. When this value is used, cross-origin resources can be fetched without giving explicit permission through the CORS protocol or the '
Cross-Origin-Resource-Policy
' header. - "
require-corp
" -
When this value is used, fetching cross-origin resources requires the server’s explicit permission through the CORS protocol or the '
Cross-Origin-Resource-Policy
' header. - "
credentialless
" -
When this value is used, fetching cross-origin no-CORS resources omits credentials. In exchange, and explicit '
Cross-Origin-Resource-Policy
' is not required. Other requests sent with credentials requires the server’s explicit permission through the CORS protocol or the 'Cross-Origin-Resource-Policy
' header.
3.2. Parsing
Step 4 about Cross-Origin-Embedder-Policy becomes:
4. If parsedItem is non-null and parsedItem[0] is "credentialless
" or "require-corp
":
-
Set policy’s value to parsedItem[0].
-
If parsedItem[1]["
report-to
"] exists, then set policy’s endpoint to parsedItem [1]["report-to
"].
Step 6 about Cross-Origin-Embedder-Policy-Report-Only becomes:
6. If parsedItem is non-null and parsedItem[0] is "credentialless
" or "require-corp
":
-
Set policy’s value to parsedItem[0].
-
If parsedItem[1]["
report-to
"] exists, then set policy’s endpoint to parsedItem[1]["report-to
"].
3.3. Compatible with cross-origin isolation
algorithm
COEP:credentialess
and COEP:require-corp
differ in the Fetch specification.
However, from the HTML specification point of view, they behave similarly. They
are referenced together with the compatible with cross-origin isolation
algorithm.
An embedder policy value is compatible with cross-origin isolation if it
it either "credentialless
" or "require-corp
".
Then replace every occurrence of:
Old | Replacement |
---|---|
coep’s value is "require-corp ".
| coep’s value is compatible with cross-origin isolation. |
coep’s value is "unsafe-none ".
| coep’s value is not compatible with cross-origin isolation. |
There are 10 occurrences to be replaced. In particular:
-
COEP:credentialless can be used to enable cross-origin isolation, the same way COEP:require-corp does.
-
If a document has a COEP policy compatible with cross-origin isolation, then the documents loaded in its
<iframe>
must also have a COEP policy compatible with , or be blocked.
4. Integration with Fetch
Note: This corresponds to the following Fetch specification change: whatwg/fetch/pull/1229
4.1. Omit credentials for no-cors cross-origin requests
Add the following algorithm:
To check Cross-Origin-Embedder-Policy allows credentials, given a request request, run theses steps:
-
If request’s mode is not
no-cors
", return true. -
If request’s client is null, return true.
-
If request’s client’s policy container’s embedder policy is not "
credentialless
", return true. -
If request’s origin is same origin with request’s current URL’s origin, return true.
-
Return false.
Then, use it in the step 8.4. of the HTTP-network-or-cache fetch algorithm:
If Cross-Origin-Embedder-Policy allows credentials with request returns false, set includeCredentials to false.
4.2. The response’s request-include-credentials attribute
In the response section, add:
A response has an associated request-include-credentials (a boolean), which is initially true.
In the http-network-or-cache-fetch algorithm. Add step:
13. Set response’s request-include-credentials to includeCredentials.
Note: This attribute is used to require corp for opaque credentialled response retrieved via CacheStorage in COEP:credentialless context. See the cache-storage-request section.
4.3. Cross-Origin-Resource-Policy internal check
Modify the step 5 from the cross-origin-resource-policy-internal-check
5. If policy is null, switch on embedderPolicyValue:
- "unsafe-none"
- Do nothing.
- "credentialless"
-
Set policy to "
same-origin
" if one of the following is true:-
response’s request-include-credentials is true.
Note: See the cache-storage-request requirement.
-
forNavigation is true.
Note: See the main-resource-request requirement.
-
response’s request-include-credentials is true.
- "require-corp"
- Set policy to "
same-origin
".
5. Integration with ServiceWorker
There are no change to the ServiceWorker specification. The CacheStorage issue: w3c/ServiceWorker/issues/1592 is entirely resolved in this section by modifying the Fetch specification.
6. Security and privacy considerations
This allows embedding cross-origin resources with no explicit opt-in into a cross-origin isolated process. This process has access to powerful features like SharedArrayBuffer or precise timers. An attacker can exploit [Spectre] more easily. The attacker can potentially read the whole process memory and read those resources. This concern has been addressed in the cross-origin-isolation section.