The Cookie Model
Cookies are scoped by domain, not origin: who can set one, who receives one, and what __Host-, Secure, Path, and Partitioned actually change.
Cookies predate the origin, and it shows
Every other web security mechanism is scoped to an origin — the triple of scheme, host, and port. Cookies are not. They were designed before the same-origin policy and are scoped to a domain, ignoring both scheme and port.
This single mismatch is the source of most cookie-related security surprises:
https://app.example.comandhttp://app.example.comare different origins but share cookies (exceptSecureones).https://app.example.com:8443andhttps://app.example.comare different origins but share cookies entirely — port is not part of cookie scope.evil.example.comcan set a cookie thatapp.example.comwill receive, if it setsDomain=example.com.
That last one is the important one, and it is why Subdomain Cookie Injection works despite SameSite. A cookie is not owned by the origin that set it.
"Site" is not "origin"
SameSite says site, deliberately. A site is the registrable domain: the public suffix plus one label, as determined by the Public Suffix List.
| A | B | Same origin? | Same site? |
|---|---|---|---|
https://app.example.com | https://app.example.com | Yes | Yes |
https://app.example.com | https://api.example.com | No | Yes |
https://app.example.com | https://evil.example.com | No | Yes |
https://app.example.com | http://app.example.com | No | No — schemeful same-site |
https://app.example.com | https://app.example.com:8443 | No | Yes |
https://a.github.io | https://b.github.io | No | No — github.io is a public suffix |
Read the third row again. A sibling subdomain is same-site. Every SameSite protection — Lax, Strict, all of it — treats a request from evil.example.com to app.example.com as same-site and sends the cookie. If an attacker controls any subdomain, SameSite has been bypassed without any technique at all.
The public-suffix row is the reason shared hosting on *.github.io, *.pages.dev, and similar is safe by default: those suffixes are on the PSL, so each subdomain is its own site.
The attributes that matter
The __Host- prefix is the underused one
Set-Cookie: csrf_token=8f3a...; Domain=example.com; Path=/; Secure
# Domain=example.com means every subdomain both RECEIVES this cookie and can
# OVERWRITE it. If any *.example.com is compromised, hosts user content, or is
# a stale CNAME someone else can claim, an attacker sets their own csrf_token
# and the double-submit check compares attacker value to attacker value.
#
# It passes. SameSite does not help: the subdomain is same-site.Why overwriting is the sharp edge
When the browser decides whether an incoming Set-Cookie replaces an existing cookie, it matches on name, domain, and path — and it does not consider the Secure flag or the scheme of the setter as part of that identity. Two consequences:
- A cookie set by
evil.example.comwithDomain=example.comand the same name occupies the same slot as the legitimate one, and the browser may send both. - When two cookies of the same name are in scope, they are sent in one header —
Cookie: csrf=attacker; csrf=legit— and the server's parser picks one, usually the first. The attacker controls the ordering by manipulating path specificity.
This is the mechanic behind cookie tossing, and it explains why naive double-submit is fragile: the defence assumes the cookie value is attacker-unreadable and attacker-unwritable, and only the first is true. See Double-Submit Cookies and Subdomain Cookie Injection.
Cookies are not the only ambient credential
The word to hold onto is ambient: a credential the browser attaches on its own, without the page asking. Cookies are the common case but not the only one, and the others are governed by no cookie attribute at all:
- HTTP Basic and Digest — once cached for a realm, re-sent automatically.
- NTLM / Negotiate (Kerberos) — transparently negotiated on intranets, which is why Windows Integrated Auth applications are so exposed.
- TLS client certificates — selected once, then reused for the origin.
All three ignore SameSite entirely. An <img> tag pointed at an intranet host protected by NTLM still authenticates in 2026.
And the negative case, which decides half of all triage arguments: Authorization: Bearer is not ambient. The browser never adds it; the application's own JavaScript does. An API authenticated purely by bearer token cannot be attacked this way. If you find yourself writing a CSRF report against such an endpoint, the finding is something else.
Related
Exactly when Lax, Strict, None, and an absent attribute attach a cookie to a cross-site request — and why the three engines disagree.
Overwriting the CSRF cookie from a sibling subdomain, why SameSite offers zero protection here, and why __Host- is the fix.
Naive double-submit and why OWASP now discourages it, then the HMAC-signed and encrypted-token variants that fix it.
The browser attaches your credentials to any request an attacker can cause. The server sees a valid session and cannot tell who asked for it.
Top-level form navigation reaches private addresses that Local Network Access does not gate, and Basic/NTLM/client-cert auth is ambient and SameSite-immune.
CORS gates reading, not sending. Simple versus preflighted requests, opaque responses, and why write-without-read is enough for CSRF.