- Motivation
- Key Scenarios
- Non-goals
- CHIPS: Opt-in Partitioned Cookies
- Design Principles
- Detailed Design
- Alternative Solutions
- Prior Art
- References and Acknowledgements
In order to increase privacy on the web, browser vendors are either planning or already shipping restrictions on cross-site tracking. This includes phasing out support for third-party cookies, cookies sent in requests to sites other than the top-level document's site, since such cookies enable servers to track users' behavior across different top-level sites.
Before CHIPS: A browser visits green.com which has an embedded red.com frame that sets a cookie. When the browser navigates to blue.com, the red.com frame can access the cookie set at green.com.
Although third-party cookies have the unfortunate consequence of enabling sites to track user behavior across different top-level sites, there are also use cases on the web today where cross-domain subresources require some notion of session or persistent state.
Some examples of such use cases are SaaS providers, headless CMS providers, and sandbox domains for serving untrusted user content, e.g. googleusercontent.com
, githubusercontent.com
(1, 2).
In these scenarios, the intention for the cookies is not to track across sites, but to provide a notion of session (or state) to embedders for a user's activity within a single top-level context.
Below are some examples of third-party cookie use cases that are unrelated to tracking that we would like to support with CHIPS. We first describe how unpartitioned third-party cookies meet that particular use case and then we describe the ideal end state would be when cross-site cookies are partitioned by top-level site.
Let's say that a page on example.com
uses an embedded third-party location service whose host is embed.map.com
, which uses a cookie to save the location of the user's favorite store location.
When the browser is on example.com
, it would send a request to embed.map.com
which would set a cookie by responding with the following header:
Set-Cookie: __Host-locationid=187; SameSite=None; Secure; HttpOnly; Path=/;
Any subsequent request to embed.map.com
would include the following header even when the browser's top-level site is no longer example.com
:
Cookie: __Host-locationid=187;
While this allows embed.map.com
to remember their favorite store location for example.com
, it also gives embed.map.com
a persistent way to identify users across different top-level sites.
Our goal is for sites like embed.map.com
to be able to set a cookie while embedded into example.com
that would only be sent when the user's browser's top-level site is example.com
.
If the user navigates to another top-level site, subsequent requests to embed.map.com
would not include the cookie set when the top-level site was example.com
.
This would enable embed.map.com
to store user preferences for their activity per-top-level-site without storing a cross-site identifier on users' machines.
Consider the site example.com
who uses a third-party CDN, static.cdn.com
to host some of its static assets.
static.cdn.com
's network uses load balancing servers which use a cookie to store the result of computing the best way to route an incoming request.
With unpartitioned third-party cookies, when a user navigates to example.com
for the first time, static.cdn.com
would respond to a browser's first request to them with the following Set-Cookie
header:
Set-Cookie: __Host-lb=a3e7; SameSite=None; Secure; HttpOnly; Path=/;
...where the value of the cookie is some string of bits that static.cdn.com
's load balancers can use to direct a request.
Subsequent requests to static.cdn.com
would include the following Cookie
header:
Cookie: __Host-lb=a3e7;
When a user navigates to another top-level site, other.com
, that also uses static.cdn.com
to serve static content.
The load balancing cookie will be sent in requests to static.cdn.com
, even though the user has never visited other.com
before.
Our goal is to allow third-party CDNs like static.cdn.com
to be able to use cookies for their load balancers but have those cookies be partitioned by top-level site.
This means that if static.cdn.com
sets a load balancing cookie on a browser on example.com
, requests to static.cdn.com
will not include that cookie when the browser navigates to other.com
.
This implies that static.cdn.com
will have to recompute the value of the load balancing cookie for each top-level site a user visits.
However, this is preferable to blocking all cookies in third-party contexts because then static.cdn.com
will have to compute the best way to route a request each time.
A partitioned cookie is also more preferable for static.cdn.com
than JavaScript storage since any data in storage would not be available until the document loads.
Consider the site example.com
now wants to use a third-party headless CMS, headless.cms.com
, to store data which example.com's custom front end code uses to render their page.
In order to tie together requests to headless.cms.com
to a single session, their server sets a cookie:
Set-Cookie: __Host-SID=c78ef; SameSite=None; Secure; HttpOnly; Path=/;
Subsequent requests to headless.cms.com
would include the following Cookie
header, even when the top-level site is no longer example.com
:
Cookie: __Host-SID=c78ef;
Our goal is to give sites like headless.cms.com
a way to set session cookies partitioned by top-level site and are available after unpartitioned third-party cookies are blocked.
These cookies could be used to identify which requests belong to the same session within a top-level site.
However, unlike the current state of the art, we do not want a cookie set when the top-level site is example.com
to be sent when the browser navigates to another site.
Some other examples of use cases for partitioned cookies not listed above are:
- Third-party CDNs that use cookies to serve access-controlled content
- Front-end frameworks that rely on remote hosting and RPCs to remote services
- Other types of third-party SaaS embeds
-
This document does not describe any changes to how a top-level site interacts with its own site's cookies.
-
This document also does not describe a replacement for third-party cookies that are shared across different domains owned by the same first party.
-
If you are using cross-site cookies between first-party domains that may be visited in top-level context, consider using First-Party Sets.
-
This document also does not describe partitioning any other type of browser storage other than cookies (e.g. HTTP cache, LocalStorage, service workers, etc.).
In order to meet the use cases, we propose to introduce partitioned cookies a.k.a. CHIPS (Cookies Having Independent Partitioned State).
Under this proposal when a user visits green.com
and embedded content from red.com
sets a cookie in response to the cross-site request, the user agent would only send that cookie when the top-level site is green.com
.
When they are visiting a new site, blue.com
, an embedded red.com
frame would not receive the cookie set when red.com
was embedded in green.com
.
After CHIPS: A browser visits green.com which has an embedded red.com frame that sets a cookie. When the user visits blue.com, the red.com frame cannot access the cookie set at green.com since it was a different top-level site.
Note: Firefox recently introduced partitioning all third-party cookies by default as a compatibility measure in the ETP Strict mode, and Safari briefly enabled (and subsequently rolled back) this in a previous version of ITP. More details on this approach are covered in the section Partition all third-party cookies by default.
The purpose of this document is to propose a new cookie attribute, Partitioned
, which will allow user agents to opt-in to partitioning cookies by top-level context, i.e. partitioned by top-level site (or that site's First-Party Set if it has one).
The primary aspect that distinguishes this proposal from existing implementations of partitioned cookies is the developer opt-in. Cookies must be set with a new attribute in order to be sent on cross-party requests once (unpartitioned) third-party cookies are obsoleted.
This principle is in line with the principle of least privilege in the long term. Initially, this new attribute will restrict a cookie's behavior, since it will limit the scope in which cookies can be sent compared to unpartitioned third-party cookies. But, in the long term these cookies will be the only cookies available in cross-party contexts.
Although existing software and APIs will need to be updated to support this new cookie attribute, we believe that an opt-in will be the best way to help move the web from (unpartitioned) third-party cookies without causing unexpected bugs. See the Partition all third-party cookies by default section below for more information.
Partitioned cookies must only be set by and sent over secure protocols. This helps address some aspects of cookies' weak confidentiality and weak integrity.
Partitioned cookies should also be hostname bound. This and the requirement partitioned cookies be sent over secure protocols makes partitioned cookies as close to origin-bound as possible. Ideally, we'd like to also have user agents scope partitioned cookies by port as well, making them origin-scoped, but we think this requirement should only be enforced if/when Origin-Bound Cookies is enabled.
Partitioned cookies should only be visible to the HTTP layer, which makes them less vulnerable to security vulnerabilities such as XSS-attacks.
Since Chrome data suggests only ~17% of cookies use the HttpOnly
attribute, we believe that requiring partitioned cookies be HTTP-only will help facilitate cookies becoming more secure overall.
Note: This requirement and the requirement to only use secure protocols makes partitioned cookies behave more similarly to HTTP State Tokens.
A third-party domain's cookie jar should have a much lower per-partition size limit than existing garbage collection thresholds (180 cookies per domain in Chrome). We propose that third-party domains be limited to just one or some small number of cookies per-partition. We chose to scope the number of cookies in a single partition per third-party by domain so that a third-party could not circumvent this limit by registering new subdomains.
Browsers may enforce some global limit on the number of partitioned cookies in the cookie jar. This is to ensure that as a user visits more top-level sites over time that the number of partitioned cookies saved to their machine does not grow over time without bound.
We propose a new cookie attribute, Partitioned
, which must be specified by the Set-Cookie
header to indicate that the cookie should be stored in a per-top-level partition.
Any cookies that are not set with the Partitioned
attribute will eventually be blocked in third-party contexts.
(Note: Other features like the SameParty
attribute may adjust the details of such blocking across domains within the same First-Party Set.)
Cookies set with the Partitioned
attribute must only be sent to the third-party host when the user agent is in the same top-level context as when the cookie was first set.
For most sites, the top-level context is just the top-level site. However, when the top-level site is part of a First-Party Set, the third-party can share the same partition across sites within the same set. This is consistent with Chrome’s privacy principle of partitioning identity by first party, and ensures that tracking across unrelated sites is prevented by the obsoletion of unpartitioned third-party cookies.
Below is an example of a Set-Cookie
header that uses the Partitioned
attribute:
Set-Cookie: __Host-SID=31d4d96e407aad42; SameSite=None; Secure; HttpOnly; Path=/; Partitioned; Set-Cookie: abc=21ef; SameSite=None; Secure // blocked in 3p contexts
In third-party contexts, the Partitioned
cookies would be sent in the request header as follows:
Cookie: __Host-SID=31d4d96e407aad42
Note: If this is a first-time request to the third-party within the current top-level context, no cookies would be sent. In other words, the third-party would get a new identifier for each top-level context.
Below is a description of how Partitioned
cookies can be used to meet the use cases laid out in the Key Scenarios section above.
Let us reconsider the third-party embed example above with embed.map.com
: a locator service which wishes to use a cookie to store user preferences for their activity on example.com
(e.g. their favorite store location).
After third-party cookies are removed, embed.map.com
could no longer set a cookie when the top-level site is not map.com
or not in a First-Party Set with map.com
, unless they include the Partitioned
attribute:
Set-Cookie: __Host-locationid=187; SameSite=None; Secure; HttpOnly; Path=/; Partitioned;
When the browser's top-level context is still example.com
, any subsequent request to embed.map.com
would include the following header:
Cookie: __Host-locationid=187;
However, when the browser navigates to a different top-level context then the browser would not send the Cookie
header above to embed.map.com
.
This gives embed.map.com
the capability to store users' favorite example.com
store location, but those preferences would only be accessible to embed.map.com
when the top-level context is example.com
.
This is to ensure that embed.map.com
cannot use this cookie to link users' activity across different top-level contexts.
Cookies with the Partitioned
attribute can meet the load balancing use case for static.cdn.com
or the session cookie for headless.cms.com
.
For the sake of brevity, let us only consider the load balancer use case.
When the user visits example.com and static.cdn.com wants to set a cookie storing the result of computing the best way to direct this particular user's requests. They could do so using the Partitioned attribute using the following Set-Cookie header:
Set-Cookie: __Host-lb=a3e7; SameSite=None; Secure; HttpOnly; Path=/; Partitioned;
This cookie would only be available to static.cdn.com
when the browser's top-level site is example.com
.
When the browser navigates to another top-level site, then subsequent requests to static.cdn.com
will not include this cookie.
One can extend this to the headless CMS example using the __Host-SID
cookie described in the Key Scenarios section.
User agents must only accept Partitioned cookies which have the __Host-
prefix.
The __Host-
prefix requires that the cookie be set with Secure
and Path=/
and disallows the Domain
attribute.
These requirements ensure that partitioned cookies only be set from and sent to secure origins only.
It also makes the cookies hostname-bound within a partition.
This requirement would have the semantics of partitioned cookies as close to origin-bound as possible.
User agents may also enforce that Partitioned
cookies also include the HttpOnly
attribute, but we are less confident they should require it.
Ensuring that partitioned cookies are only available on the network stack makes them less susceptible to XSS attacks.
User agents may only accept Partitioned
cookies if their SameSite
attribute is None
.
Note: a Partitioned
cookie without SameSite=None
is effectively just a same-site cookie which cannot be sent in a third-party context anyway.
User agents should reject any cookie set with both Partitioned
and SameParty
attributes.
Since sites within the same First-Party Set are allowed access to unpartitioned SameParty
cookies, the semantic is inconsistent with partitioned cookies.
If a top-level site sends Clear-Site-Data
, then the user agent clears all partitioned cookies available to third-parties from that site as well.
If a third-party site sends Clear-Site-Data
, then the user agent should clear all cookies available to that third-party in the partition for the current top-level site alone.
The user agent must not clear the third-party's cookies in other partitions.
This is to prevent abuse of such a capability as a cross-site tracking vector as described here.
Browsers may choose to provide user controls to clear individual partitions of a site’s cookies.
The new cookie attribute will be ignored on older clients that don't recognize it and fall back to default behavior.
Since these cookies are intended for third-party contexts, clients that are incompatible with SameSite=None
may reject cookies with SameSite=None
.
It is also recommended to still include the __Host-
prefix.
Even clients that do not recognize the Partitioned
attribute still enforce the semantics of the __Host-
prefix.
This would ensure that cross-site cookies are hostname bound and only sent over secure channels, which is still a security win.
Service workers have access to cookies via the CookieStore API or when they send HTTP requests using fetch
(imagine a worker pings an HTTP endpoint that just echoes back the request's Cookie
header in its response).
Unless service workers are partitioned, then the unpartitioned cookie jar would be available to the worker even if the cookies are HttpOnly
.
Because of these reasons, partitioning service workers is the only way to guarantee a partitioned cookie jar.
Safari has already partitioned service workers by the top-level origin when the worker was registered and the service worker's origin, so that service workers can only interact with windows that are the same top-level origin as the top-level page when the worker was installed.
If a user agent partitions service workers using this scheme, there is no cross-site tracking risk to exposing Partitioned
cookies to service workers.
Service workers are disabled in Firefox when Dynamic Partitioning is enabled, but they are working on implementing a partitioned service worker solution.
Firefox announced that they are partitioning all third-party cookies by default into their ETP Strict mode. Safari previously tried partitioning cookies based on heuristics, but eventually chose to block them altogether citing developer confusion as one of the reasons.
We do not think cookies should be partitioned without a developer opt-in since developers built their existing servers with the expectation of an unpartitioned third-party cookie. This can cause confusion and unexpected bugs (1, 2, 3, 4).
Partitioning by default also has more implementation complexity for browser developers, since they need to consider how partitioning the cookie jar will impact any part of the browser that interacts with cookies. Supporting opt-in cookie partitioning while gradually moving the web off of globally-scoped third-party cookies will help ease the transition for browsers.
There is also the issue of state proliferation. There are some third-party origins on the web today that are prevalent across many top-level contexts. If we partition the cookie jar by default and do not include a new upper bound on the size of each cookie jar partition, device storage limits will be exhausted more quickly.
One additional limitation user agents may also enforce is to limit the number of cookies in a top-level context's partition across all third-party domains as well. This limit would prevent a single top-level context's partition from taking up too much space in the cookie jar.
We chose not to enforce a global per-partition limit is that it would open a side channel for a third party to learn if another, distinct third party set a cookie within the same top-level context.
For example, say each third-party domain is restricted to 1 cookie per partition and the global per-partition limit was 10.
A malicious third party could embed frames from several domains, evil[1-10].com
on some top-level site, 1p.com
, which sets a partitioned cookie.
If any other third party sets a partitioned cookie on 1p.com
, then one of the evil[1-10].com
cookies will be evicted and the malicious third party will learn that another, distinct host set a cookie.
A malicious third party could use this information to determine if a user has logged in or if they may be using a locator service hosted by a distinct party.
Another attack is where evil.com
communicates with other third parties by setting cookies only based on a user’s personal attributes or preferences.
One way to potentially circumvent this is to make the global per-partition limit much larger than the per-partition limit for each third-party domain, but it is unclear what the relative size of the global per-partition limit would have to be to mitigate these attacks.
Cookies with the __Host-
prefix implicitly have the same properties as cookies with the __Secure-
prefix.
By requiring partitioned cookies to have the former we guarantee that they also have the same properties as if we required the latter.
One alternate design choice is to not require that cookies with the Partitioned
attribute have a __Host-
prefix.
Instead, the semantics of the Partitioned
attribute would include the semantics of __Host-
prefix cookies (i.e. requiring Secure
and Path=/
, disallowing Domain
).
We decided against this for two reasons.
The first is that clients that do not yet recognize the Partitioned
attribute may still recognize the __Host-
prefix and can still benefit from its semantics.
The second is that mixing the semantics of prefixes and attributes is not the right path forward, since it makes the semantics of either more difficult to understand.
Websites can choose to delegate/alias a subdomain to a third-party service provider using DNS CNAME records.
For example, a site myblog.example
may have a subdomain foo.myblog.example
that can be mapped to a third-party endpoint dedicated to servicing that site at myblog.cms.example
.
In this case, the browser treats foo.myblog.example
as first-party with the top-level site, and any cookies sent on the request that eventually gets sent to myblog.cms.example
are implicitly partitioned on [foo.]myblog.example
.
It follows that any cookies sent to foo.myblog.example
would not be subjected to cross-site cookie restrictions and would still be sent to myblog.cms.example
.
However, this pattern has a couple of security drawbacks:
-
myblog.cms.example
would need to acquire and serve TLS certificates issued forfoo.myblog.example
. -
All
Domain
cookies set onmyblog.example
get sent tomyblog.cms.example
, including potentially sensitive data set by other subdomains onmyblog.example
.
Additionally, this adds implementation and deployment complexity for developers.
- Safari partitioned the cookie jar by default in ITP 2.1
- Firefox partitioned the cookie jar by default in ETP Strict Mode
We’d like to thank Lily Chen, Steven Bingler, Rowan Merewood, and Jeffrey Yasskin for their insights and advice that helped us shape this proposal.
- cfredric/sameparty
- Chromium Blog: Building a more private web: A path towards making third party cookies obsolete
- Clear-Site-Data for partitioned storage can be used for cross-site tracking · Issue #11 · privacycg/storage-partitioning
- Cookie Store API Explainer | cookie-store
- cookie_monster.h - Chromium Code Search
- draft-ietf-httpbis-rfc6265bis-07 - Cookies: HTTP State Management Mechanism
- [Dynamic FPI] The user and password for Facebook did not transfer to messenger.com
- Firefox 86 Introduces Total Cookie Protection - Mozilla Security Blog
- [FirstPartyIsolation] Failed to sign in to the pixnet.net
- Fx with FPI feature wrongly displays that sign-in on youtube has failed even though it did not
- Googleusercontent.com can trip you up, if you disable third-party cookies · Kerika
- Headless CMS Github Gist · LOGIN-issues.md
- Headless content management system - Wikipedia
- Intelligent Tracking Prevention 2.1 | WebKit
- Isolate service workers and DOM cache by first party domain
- michaelkleber/privacy-model: A Potential Privacy Model for the Web: Sharding Web Identity
- mikewest/http-state-tokens: Incrementally better HTTP state management.
- Principle of least privilege - Wikipedia
- privacycg/first-party-sets
- SameSite=None: Known Incompatible Clients - The Chromium Projects
- sbingler/Origin-Bound-Cookies
- Software as a service use case for FPS · Issue #33 · privacycg/first-party-sets
- State Partitioning - Mozilla | MDN
- View Source shows source code of login page instead of current webpage on local django server
- Workers at Your Service | WebKit
- [Working] Fix Google Drive Downloads Not Working in Microsoft Edge – Gadgets To Use