Storage Access Headers

Unofficial Proposal Draft,

More details about this document
This version:
https://cfredric.github.io/storage-access-headers
Issue Tracking:
GitHub
Inline In Spec
Editor:
(Google)

Abstract

This document defines a pair of request and response headers that aim to give servers information about whether unpartitioned cookies were (or could be) included on a request, and provide the ability for servers to allow those cookies to be sent (if the user has already relaxed the privacy boundary via the Storage Access API).

Status of this document

1. Introduction

The Storage Access API supports "authenticated embeds" by providing a way to request access to unpartitioned cookies in an embedded context. This currently requires an explicit call to a JavaScript API (namely requestStorageAccess()) to:

  1. Potentially prompt the user for permission; and

  2. Explicitly indicate the embedded resource’s interest in using unpartitioned cookies (as a protection against CSRF attacks by an embedder).

As the above list suggests, this single API invocation is serving two orthogonal purposes:

  1. It enforces a privacy boundary between the top-level site and the embedded site, and gives the user (and/or user agent) an opportunity to relax or maintain that privacy boundary.

  2. It enforces a security boundary between the top-level site and the embedded site (namely, the aforementioned CSRF protection), and serves as the embedded site’s explicit signal to relax that security boundary (by allowing credentialed requests to be sent to the embedded site, in the given context).

The requirement to invoke requestStorageAccess is therefore useful, but it imposes some challenges:

These challenges can be mitigated by supporting a new pair of headers. In particular, this document introduces:

2. Infra

This specification depends on the Infra standard. [INFRA]

3. Storage-Access Request Infrastructure

In addition to the new headers themselves, this document introduces some new infrastructure to store and convey metadata in the user agent, particularly on a request.

A request has a boolean eligible for storage-access. It is initially false.

Note: The eligible for storage-access boolean indicates whether the user agent is allowed to include unpartitioned cookies when sending a request where the site obtained from the request’s url has a storage-access permission.

Note: A request also has a has storage access boolean, indirectly through its client. That value is distinct from the request's eligible for storage-access boolean. Both represent an "opt in" signal for accessing unpartitioned cookies in a cross-site context, but the signal comes from different origins. The request's client's has storage access field represents whether the site obtained from the environment's origin has opted in. The request's eligible for storage-access field represents whether the site obtained from the request's url's origin has opted in.

A request has an associated single-hop cache mode, whose value is null or a cache mode. It is initially set to null.

This document renames a request's cache mode field to internal cache mode.

This document redefines a request's cache mode as the cache mode returned by running the following steps, given a request request:
  1. If request’s single-hop cache mode is not null, return request’s single-hop cache mode.

  2. Return request’s internal cache mode.

A storage access status is one of "none", "inactive", or "active".

The storage access status of a request request is the storage access status-or-null returned by running the following steps:
  1. If the user agent’s cookie store would attach cookies with the SameSite=Strict attribute to request, return null. [COOKIES]

  2. Let allowed be a boolean, initially set to the result of determining whether the user agent’s cookie store allows unpartitioned cookies to be accessed given request’s url, request’s client, and request’s eligible for storage-access.

  3. If allowed is true, return "active".

  4. If request’s eligible for storage-access is true, return "none".

    Note: the "storage-access" policy-controlled feature was checked before setting request’s eligible for storage-access to true.

  5. Let featureIsAllowed the result of running Should request be allowed to use feature? given "storage-access" and request.

  6. If featureIsAllowed is false, return "none".

  7. Set allowed to the result of determining whether the user agent’s cookie store allows unpartitioned cookies to be accessed given request’s url, request’s client, and true.

  8. If allowed is true, return "inactive".

    Note: allowed will be true in the above step if the permission store entry obtained by getting a permission store entry given a PermissionDescriptor with name initialized to "storage-access" and a permission key of (the site obtained from request's client's top-level origin, the site obtained from request's url's origin) has a state of "granted". Otherwise, allowed will remain false.

  9. Return "none".

To determine whether the user agent’s cookie store allows unpartitioned cookies to be accessed, given a url url, an environment settings object environment, and a boolean eligible for storage-access, run the following steps (which return a boolean):
  1. Let top level site be the result of obtaining a site from environment’s top-level origin.

  2. Let destination site be the the result of obtaining a site from url’s origin.

  3. Let key be (top level site, destination site).

  4. Let allowed be the result of determining whether the user agent explicitly allows unpartitioned cookie access given key.

  5. If allowed is true, return true.

  6. Return false if all of the following conditions are false:

  7. Let entry be the result of getting a permission store entry given a PermissionDescriptor with name initialized to "storage-access" and a permission key of key.

  8. If entry’s state is not "granted", return false.

  9. Return true.

4. Storage-Access Headers

The following sections define a request header and a response header. The request header exposes information about the request's access to cookies to a server. The response header allows a server to opt into accessing unpartitioned cookies on a particular request or when loading an iframe.

4.1. The Sec-Fetch-Storage-Access HTTP Request Header

The Sec-Fetch-Storage-Access HTTP request header exposes a request's ability or inability to access cookies to a server. It is a Structured Field item whose value MUST be a token. [I-D.structured-field-values-for-http] Its ABNF is:

Sec-Fetch-Storage-Access = sf-token

Valid Sec-Fetch-Storage-Access values include "none", "inactive", and "active". In order to support forward-compatibility with as-yet-unknown semantics, servers SHOULD ignore this header if it contains an invalid value.

// When the request’s credentials mode is "omit", the header is omitted:


// When the request is same-site, the header is omitted:


// When the request has no access to unpartitioned cookies, the header’s value is "[=storage access status/none=]":
Sec-Fetch-Storage-Access: none

// When the request has no access to unpartitioned cookies, but
// 'storage-access' permission has already been granted, the header’s value is
// "[=storage access status/inactive=]":
Sec-Fetch-Storage-Access: inactive

// When the request has access to unpartitioned cookies, the header’s value is "[=storage access status/active=]":
Sec-Fetch-Storage-Access: active
To set the Sec-Fetch-Storage-Access header for a request request:
  1. Assert: request’s url is a potentially trustworthy URL.

  2. If request’s credentials mode is not "include", abort these steps.

  3. Let access be request’s storage access status.

  4. If access is null, abort these steps.

  5. Let value be a Structured Field value whose value is a token.

  6. Set value’s value to access.

  7. Set a structured field value given ("Sec-Fetch-Storage-Access", value) in request’s header list.

4.2. The Activate-Storage-Access HTTP Response Header

The Activate-Storage-Access HTTP response header allows a server to opt in to accessing its unpartitioned cookies in a cross-site request context. It is a Structured Field item whose value MUST be a token. [I-D.structured-field-values-for-http] Its ABNF is:

Activate-Storage-Access = sf-item

Valid Activate-Storage-Access values include "load" and "retry".

The following parameter is defined:

// The server’s response requests that the user agent activate storage access
// before continuing with the load of the resource. (This is only relevant when
// loading a new document.)
Activate-Storage-Access: load

// The server’s response requests that the user agent activate storage access,
// then retry the request. The "allowed-origin" parameter allowlists the
// request’s origin.
Activate-Storage-Access: retry; allowed-origin="https://foo.bar"

// Same as above, but using a wildcard instead of explicitly naming the request’s origin.
Activate-Storage-Access: retry; allowed-origin=*
To perform a storage access retry check for a request request and response response, run the following steps:
  1. If request’s credentials mode is not "include", return failure.

  2. If request’s eligible for storage-access is true, return failure.

  3. Let storageAccessStatus be request’s storage access status.

  4. If storageAccessStatus is not "inactive", return failure.

  5. Let parsedHeader be the result of getting a structured field value given "Activate-Storage-Access" and "item" from response’s header list.

  6. If parsedHeader is null, return failure.

  7. Let (value, params) be parsedHeader.

  8. If value is not a token, return failure.

  9. If value’s value is not "retry", return failure.

  10. If params["allowed-origin"] does not exist, return failure.

  11. Let allowedOrigin be params["allowed-origin"].

  12. If allowedOrigin is a token whose value is "*", return success.

  13. If allowedOrigin is not a string, return failure.

  14. If the result of byte-serializing a request origin with request is not allowedOrigin’s value, then return failure.

  15. Return success.

To perform a storage access load check for a request request and response response, run the following steps:
  1. Let storageAccessStatus be request’s storage access status.

  2. If storageAccessStatus is not one of "inactive" or "active", return failure.

  3. Let parsedHeader be the result of getting a structured field value given "Activate-Storage-Access" and "item" from response’s header list.

  4. If parsedHeader is null, return failure.

  5. Let (value, params) be parsedHeader.

  6. If value is not a token, return failure.

  7. If value’s value is not "load", return failure.

  8. Return success.

5. Integration with Fetch Metadata

The `Sec-Fetch-Storage-Access` header is appended to outgoing requests alongside other Fetch Metadata headers. [FETCH-METADATA] Modify the definition of append the Fetch metadata headers for a request by inserting the following as step 6:

  1. Set the Sec-Fetch-Storage-Access header for r.

6. Integration with Fetch

Handling these headers requires modifications to a few different parts of Fetch. [FETCH]

6.1. Origin header

When making a decision on whether to retry a request and force it to include unpartitioned cookies, a server ought to be informed as to the initiator of the request. I.e., the request ought to include the `Origin` header whenever it also includes the Sec-Fetch-Storage-Access: inactive header. Modify the definition of append a request Origin header by rewriting step 4 as:

  1. If at least one of the following conditions is true:

    Then:

The rest of the algorithm is unmodified.

6.2. HTTP-fetch

Insert a new step after step 5 in HTTP fetch:

  1. If the result of performing a storage access retry check for request is success, then return the result of running HTTP-storage-access-retry-fetch given fetchParams.

Insert a new step after step 6.1 (before "switch on request’s redirect mode"):

  1. Set request’s single-hop cache mode to null.

The rest of the algorithm is unmodified.

To HTTP-storage-access-retry-fetch given a fetch params fetchParams, run the following steps:
  1. Let request be fetchParams’s request.

  2. Assert: request’s storage access status is "inactive".

  3. Assert: request’s eligible for storage-access is false.

  4. If request’s redirect count is 20, then return a network error.

  5. Increase request’s redirect count by 1.

  6. Append request’s url to request’s URL list.

  7. Set request’s single-hop cache mode to "reload".

  8. Set request’s eligible for storage-access to true.

  9. Assert: request’s storage access status is "active".

  10. Let recursive be true.

  11. Return the result of running main fetch given fetchParams and recursive.

6.3. HTTP-redirect-fetch

Insert a new step after step 17 in HTTP-redirect fetch:

  1. If locationURL’s origin is not same origin with request’s url's origin, set request’s eligible for storage-access to false.

The rest of the algorithm is unmodified.

7. Integration with HTML

This integration builds upon the changes introduced by the Storage Access API specification. [STORAGE-ACCESS]

In particular, modify the changes when creating the request’s reserved client in create navigation params by fetching to be the following:

  1. Let compute has storage access be an algorithm with the following steps, which return a boolean:

    1. If response is not null and the result of performing a storage access load check given request and response is success, return true.

    2. If sourceSnapshotParams’s environment id does not equal navigable’s active document's relevant settings object's id, return false.

    3. If originalURL’s origin is not same origin with currentURL’s origin, return false.

    4. If response is not null and response’s has-cross-origin-redirects is true, return false.

    5. Return true.

  2. Set request’s reserved client's has storage access to the result of executing compute has storage access.

8. Security Considerations

8.1. Opt-In signal

The primary security concerns for this specification are those laid out in privacycg/storage-access#113. Namely: since the Storage Access API makes unpartitioned cookies available even after those cookies have been blocked by default, it is crucial that the Storage Access API not preserve the security concerns traditionally associated with unpartitioned cookies, like CSRF. The principal way that the Storage Access API addresses these security concerns is by requiring an embedded cross-site resource (e.g. an iframe) to explicitly opt in to accessing unpartitioned cookies by invoking requestStorageAccess().

Storage Access Headers continues in the same vein by requiring embedded cross-site resources (or rather, their servers) to explicitly opt-in to accessing unpartitioned cookies (by supplying an HTTP response header), before any unpartitioned cookies are included on the request. When a server opts in by sending the Activate-Storage-Access: retry header, it also must explicitly name the origin that it grants the ability to send credentialed requests (via the "allowed-origin" parameter). This fails closed by blocking credentialed requests, in the event of an origin mismatch.

8.2. Forbidden header name

This proposal uses a new forbidden name for the `Sec-Fetch-Storage-Access` header to prevent programmatic modification of the header value. This is primarily for reasons of coherence, rather than security, but there is a security reason to make this choice. If a script could modify the value of the header, it could lie to a server about the state of the storage-access permission in the requesting context and indicate that the state is "active", even if the requesting context has not opted in to using the permission grant. This could mislead the server into inferring that the request context is more trusted/safe than it actually is (e.g., perhaps the requesting context has intentionally not opted into accessing its unpartitioned cookies because it cannot conclude it’s safe to do so). This could lead the server to make different decisions than it would have if it had received the correct header value ("none" or "inactive"). Thus the value of this header ought to be trustworthy, so it ought to be up to the user agent to set it.

8.3. Deeper CORS Integration

It is tempting to design this specification such that it piggy-backs and/or integrates with CORS (i.e., the CORS protocol) deeply, since CORS intuitively feels like it is meant to address a similar problem of enabling cross-origin functionality. However, this would be undesirable for a few reasons:

Therefore, CORS ought to be neither necessary nor sufficient for attaching unpartitioned cookies to a cross-site request. This specification is therefore designed to be orthogonal to CORS.

Note: This specification does rely on the `Origin` header, which is defined by CORS, so this specification does integrate with CORS in a technical sense. This is intentional, since in that case we are able to reuse an existing header that sends exactly the information that this specification needs, and both the new usage and existing usage are for security features.

9. Privacy Considerations

This specification simplifies some ways in which developers can use an API that allows access to unpartitioned data. However, it does not meaningfully change the privacy characteristics of the Storage Access API [STORAGE-ACCESS]: sites are still able to ask for the ability to access unpartitioned cookies; user agents are still able to handle those requests how they see fit. Importantly, if the storage-access permission is not granted by the user or user agent, then this specification does not allow use of unpartitioned data.

The `Sec-Fetch-Storage-Access` header does expose some user-specific state in network requests which was not previously available there, namely the state of the storage-access permission. However, this information is not considered privacy-sensitive, for a few reasons:

10. Deployment Considerations

10.1. Vary

If a given endpoint might use the `Activate-Storage-Access` header, then developers should include `Sec-Fetch-Storage-Access` in the response’s Vary header [RFC9110], to ensure that caches handle the response appropriately. For example, Vary: Accept-Encoding, Sec-Fetch-Storage-Access.

10.2. Origin Header Interoperability

Some servers misbehave if they receive the `Origin` header when they weren’t expecting to. However, the `Origin` header conveys exactly the information that a server would need before making an informed choice on whether to respond with the Activate-Storage-Access: retry header, so it’s a perfect candidate for reuse by this specification (rather than inventing some new Origin2 header). This specification strives to minimize new breakage due to including the `Origin` header on more requests, by minimizing the set of requests that newly include the `Origin` header. In particular, the `Origin` header is only (newly) included on cross-site requests whose storage access status is "inactive" and whose credentials mode is "include".

10.3. Sec- Prefix

The `Sec-Fetch-Storage-Access` header’s name is prefixed with Sec- because only the user agent is permitted to set such headers (as they are forbidden request-headers). Therefore, the `Sec-Fetch-Storage-Access` name is guaranteed to not conflict with any preexisting headers in use on the web.

10.4. Header Compression

The `Sec-Fetch-Storage-Access` header has exactly 3 legal values. Therefore it should perform well with HPACK, per [MNOT-DESIGNING-HEADERS]. Optimizing the length of the header name has little impact compared to minimizing the number of legal header values.

The `Activate-Storage-Access` header has an unbounded number of legal values, but only a small number of them (perhaps 1-2) can reasonably be expected to occur in a single HTTP connection. This header should therefore also perform reasonably well with HPACK.

11. IANA Considerations

The permanent message header field registry should be updated with the following registrations for the headers defined in this specification: [RFC3864]

11.1. Sec-Fetch-Storage-Access Registration

Header field name

Sec-Fetch-Storage-Access

Applicable protocol

http

Status

draft

Author/Change controller

Me

Specification document

This specification (See § 4.1 The Sec-Fetch-Storage-Access HTTP Request Header)

11.2. Activate-Storage-Access Registration

Header field name

Activate-Storage-Access

Applicable protocol

http

Status

draft

Author/Change controller

Me

Specification document

This specification (See § 4.2 The Activate-Storage-Access HTTP Response Header)

12. Acknowledgements

Thanks to Johann Hofmann, Artur Janc, Ben VanderSloot, Dom Farolino, Matt Menke, Adam Rice, and Maks Orlovich, who all provided valuable insight and support in the design of this mechanism.

Conformance

Document conventions

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.

Tests

Tests relating to the content of this specification may be documented in “Tests” blocks like this one. Any such block is non-normative.


Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[COOKIES]
A. Barth. HTTP State Management Mechanism. April 2011. Proposed Standard. URL: https://httpwg.org/specs/rfc6265.html
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[FETCH-METADATA]
Mike West. Fetch Metadata Request Headers. URL: https://w3c.github.io/webappsec-fetch-metadata/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[I-D.structured-field-values-for-http]
Mark Nottingham; Poul-Henning Kamp. Structured Field Values for HTTP. ID. URL: https://datatracker.ietf.org/doc/html/rfc8941
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[PERMISSIONS]
Marcos Caceres; Mike Taylor. Permissions. URL: https://w3c.github.io/permissions/
[PERMISSIONS-POLICY-1]
Ian Clelland. Permissions Policy. URL: https://w3c.github.io/webappsec-permissions-policy/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[RFC3864]
G. Klyne; M. Nottingham; J. Mogul. Registration Procedures for Message Header Fields. September 2004. Best Current Practice. URL: https://www.rfc-editor.org/rfc/rfc3864
[RFC9110]
R. Fielding, Ed.; M. Nottingham, Ed.; J. Reschke, Ed.. HTTP Semantics. June 2022. Internet Standard. URL: https://httpwg.org/specs/rfc9110.html
[SECURE-CONTEXTS]
Mike West. Secure Contexts. URL: https://w3c.github.io/webappsec-secure-contexts/
[STORAGE-ACCESS]
The Storage Access API. Editor's Draft. URL: https://privacycg.github.io/storage-access/
[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/

Informative References

[MNOT-DESIGNING-HEADERS]
Mark Nottingham. Designing Headers for HTTP Compression. URL: https://www.mnot.net/blog/2018/11/27/header_compression

Issues Index

This condition ought to check a boolean that is updated after cross-site HTTP redirects. I.e., it ought to read a boolean derived from the environment’s has storage access, not that value itself. See storage-access#210.