Skip to content

Cookies: attributes, SameSite and partitioning

SameSite asks which site started the request, and a site is a registrable domain, not an origin.

Predict first

Before you read: predict

ExamHall's stand-alone candidate app on https://exam.examhall.test has set __Host-session=…; Secure; HttpOnly; Path=/; SameSite=Lax, and the candidate is signed in. Then four requests reach exam.examhall.test:

  1. On the university course page https://lms.uni.test, the candidate clicks a link to /attempts (GET).
  2. The same course page shows an <img> from /logo.gif.
  3. The proctor console on https://proctor.examhall.test shows an <img> from /status.gif.
  4. A form on the course page POSTs to /attempts/start.

Which requests carry the session cookie?

Pick one

The 30-second version

The browser, not the page, attaches cookies. Domain widens a cookie to a domain plus subdomains, Path narrows it; Secure needs HTTPS; HttpOnly hides it from script. SameSite compares sites (scheme plus registrable domain), not origins: Strict sends only on same-site requests, Lax also on cross-site top-level GET navigations, None everywhere, with Secure. Partitioned keeps one jar per top-level site.

SameSite asks which site started the request, and a site is a registrable domain, not an origin.

How it works

Step 1 of 11

Set-Cookie stores a name, a value and attributes. On every later request the browser decides whether to attach it; page code never chooses.

Analogy: A diplomatic bag

A ministry hands its envoy sealed envelopes. On every trip the envoy, not the people she visits, reads each envelope's labels and decides whether it travels.

In the analogy In the browser
"For the main building only", or "for the main building and all its annexes" host-only, or Domain (step 2)
"Armoured car only"; "never show to local staff" Secure; HttpOnly (step 3)
The ministry and its annexes are one government, whichever building one site, many origins (step 5)
Carried only on errands the ministry itself started Strict (step 6)
Also carried when a foreign office invites her to visit in person, to look, not to sign Lax: a cross-site top-level GET (step 7)
Carried on any errand, but only in the armoured car None needs Secure (step 8)
A separate bag for each host country she serves in Partitioned (step 10)

Breaks down when: an envoy judges an errand's purpose; the browser checks only sites and the request's kind. Wrong inference: "only legitimate visits carry a Lax cookie." A forged GET navigation carries it too (step 11).

Contrasting case: a passport the envoy shows only when her own staff hand it over. That is a token the page stores and sends in a header itself: page code decides, and script can read it.

In real terms: for each request the browser filters the jar by host, path and scheme, then compares the request's initiating site with the cookie's site under its SameSite mode, and, for a partitioned cookie, the top-level site with its partition key.

Examples, run for real

4.1 ExamHall stand-alone: which cookies the browser keeps

The app tries six Set-Cookie lines on exam.examhall.test; the fourth arrives over plain HTTP.

https __Host-session=s1; Secure; HttpOnly; Path=/; SameSite=Lax                      -> stored
https __Host-legacy=s2; Secure; HttpOnly; Path=/; Domain=examhall.test; SameSite=Lax -> rejected
https widget=w1; Path=/; SameSite=None                                               -> rejected
http  pref=p1; Secure; Path=/; SameSite=Lax                                          -> rejected
https theme=dark; Secure; Path=/; SameSite=Lax                                       -> stored
https draft=d1; Secure; Path=/attempts; SameSite=Lax                                 -> stored
request GET /attempts/7 carries: __Host-session, draft, theme
document.cookie on /attempts/7: "draft=d1; theme=dark"
request GET /help carries: __Host-session, theme

A Domain breaks the __Host- rule (step 4); None without Secure and Secure over HTTP are refused (steps 3, 8). Script never sees the HttpOnly session, yet every request carries it (step 3). draft stays under /attempts (step 2).

4.2 ExamHall in the LMS iframe: EXH-657 and MR !977

EXH-657: embedded in a university LMS, ExamHall showed "Signed out" on every question. The LMS launches ExamHall inside an iframe, whose response sets the cookie; the iframe then loads a question. The same candidate later opens a second university's LMS.

third-party cookies allowed (default profile):
  Lax (the stand-alone cookie)   | stored: nothing                              | in lms.uni.test: not sent | in lms.otheruni.test: not sent
  None; Secure                   | stored: __Host-embed                         | in lms.uni.test: sent     | in lms.otheruni.test: sent
  None; Secure; Partitioned      | stored: __Host-embed [key uni.test]          | in lms.uni.test: sent     | in lms.otheruni.test: not sent
third-party cookies blocked (DevTools Protocol cookie controls):
  Lax (the stand-alone cookie)   | stored: nothing                              | in lms.uni.test: not sent | in lms.otheruni.test: not sent
  None; Secure                   | stored: nothing                              | in lms.uni.test: not sent | in lms.otheruni.test: not sent
  None; Secure; Partitioned      | stored: __Host-embed [key uni.test]          | in lms.uni.test: sent     | in lms.otheruni.test: not sent

A cross-site iframe is not a top-level navigation, so Lax is never even stored (step 7). Plain None works only while third-party cookies are allowed, and then follows the candidate into every LMS. MR !977 issues a separate partitioned embed cookie: one jar per university, surviving third-party blocking (step 10). The stand-alone cookie stays Lax.

4.3 Proof domain, ticketing: four SameSite modes, five requests

A fan forum on another site makes five requests to tix.ticketco.test, which holds four cookies differing only in SameSite:

request from fanforum.test   | strict | lax    | none   | unset 
link (top-level GET)         | -      | sent   | sent   | sent  
GET form (top-level)         | -      | sent   | sent   | sent  
POST form (top-level)        | -      | -      | sent   | sent  
<img>                        | -      | -      | sent   | -     
<iframe>                     | -      | -      | sent   | -     
unset cookie as stored by Chromium: sameSite=Lax

Strict goes nowhere cross-site (step 6); Lax only on top-level GETs (step 7). The unset column is the draft's Lax default with its 2-minute POST allowance, as Chromium implements them (step 9): a one-off run printed unset for a 1-second-old cookie and none at 125 seconds. Firefox does not default to Lax (a missing SameSite acts as None), though its default third-party partitioning changes the embedded rows; Firefox was not run.

Picture it

a man in a green jacket walks towards a desk carrying a green box on a shoulder strap, full of small sealed envelopes. Behind the desk a woman in a blue jacket holds up one sealed envelope. On the desk stand two closed boxes, one green and one blue, each with a small clasp.
An illustration to help it stick. The mechanism is in the sections above.

Try it

Answer first, then check. Nothing is saved.

Question 1 of 5

A chat app keeps its session in __Host-chat=…; Secure; HttpOnly; Path=/; SameSite=Strict on https://app.chatco.test. A signed-in user reads an invite in webmail on https://mail.webmail.test and clicks its link to the room page https://app.chatco.test/room/42. The room page then loads its own avatar image from https://app.chatco.test/avatar.gif.

Which of these two requests carries the session cookie?

Pick one

Question 2 of 5

A ticket site on https://tix.ticketco.test moves a ticket on GET /tickets/transfer?to=…. Its session cookie is __Host-tix=…; Secure; HttpOnly; Path=/; SameSite=Lax. A signed-in fan opens a page on https://evil.test that tries three things: a script redirect (location.href = …/tickets/transfer?to=mallory), an <img> with that URL, and a hidden form that POSTs to it.

Which attempts carry the session cookie?

Pick one

Question 3 of 5

https://shop.catalog.test has set four cookies: sid=…; Secure; HttpOnly; Path=/, cart=3; Secure; Path=/cart, cur=EUR; Secure; Path=/ and ab=B; Secure; Domain=catalog.test; Path=/, all SameSite=Lax. Script on https://shop.catalog.test/products/42 reads document.cookie.

Which cookie names does it see?

Pick one

Question 4 of 5

StreamCo's viewer app on https://watch.streamco.test keeps its session in __Host-sid=…; Secure; HttpOnly; Path=/; SameSite=Strict. Creators publish their own HTML pages on https://creators.streamco.test. One creator page auto-submits a hidden form that POSTs to https://watch.streamco.test/account/email, and signed-in viewers who open it get their account email changed. The team says Strict should have stopped this.

Which is the bug?

Pick one

Question 5 of 5

A shop on https://shop.merchant.test sets pay_ref=…; Secure; HttpOnly; Path=/pay; SameSite=Lax, then sends the buyer to the bank's check page on https://acs.bank.test. When the check is done, the bank's page auto-submits a form that POSTs to https://shop.merchant.test/pay/return. The shop never finds pay_ref there, so the payment cannot be matched.

Which change makes pay_ref arrive?

Pick one

Go deeper

Pro tips
  1. Log Sec-Fetch-Site on the server to see how the browser classified a request. It reads same-origin, same-site, cross-site or none.
    • When it helps: a cookie "sometimes" goes missing and you need to know whether the browser saw the request as cross-site (steps 5–7).
    • Status: RAN (Chromium 153.0.8010.12).
  2. In DevTools, open the request in the Network panel and check its Cookies tab, with "show filtered out request cookies" ticked. Chrome lists each withheld cookie with the reason, such as its SameSite setting.
Source

Checked against the tools and docs as of 2026-09-27.