Web Platform · Web Security Model
Cookie Scoping, SameSite, and the Site Boundary
How cookies are addressed by host and path rather than of origin, what SameSite Lax and Strict actually permit, and where domain-scoped cookies leak across unrelated applications.
Cookies were designed before the origin triple was standardised, with a different assumption: that a hostname identifies one administrative domain. The result is a storage mechanism whose scope is host and path, with no port component and no scheme component, plus a separate site-level control bolted on years later. Almost every cookie-related vulnerability comes from the gap between what developers believe the scope is and what the browser actually sends.
The addressing rule is host and path, never origin
A cookie has a name, a value, a domain, a path, an expiry, and a set of flags. When a request is issued, the browser walks every stored cookie and sends those whose domain matches the request host and whose path matches the request path, ordered by path specificity.
Domain matching has two modes. A cookie set without a Domain attribute is host-only: it is sent only to the exact host that set it, so https://a.example.com cannot read a host-only cookie set by https://b.example.com. A cookie set with Domain=example.com is sent to example.com and every subdomain of it. The leading dot in Domain=.example.com is accepted for historical reasons and ignored. A server cannot widen a cookie beyond its own registrable domain: the browser rejects a Domain value that the request host does not domain-match, and it rejects a Domain value that is a public suffix such as com or github.io. That rejection is the only thing standing between a hosting platform and total cookie confusion.
Path matching is prefix matching with a boundary rule. A cookie with Path=/app is sent for /app, /app/, and /app/settings, but not for /application. A cookie set with no Path gets the default path: the request path up to but not including its final /, or / when the path contains only one segment. When several cookies share a name and different paths, all are sent, ordered from longest path to shortest, and the server is responsible for deciding which to believe — a detail that matters in the cookie-tossing cases below.
Nothing in this addressing rule refers to the origin triple. Cookies ignore the port entirely: a cookie set by http://example.com:8080 is sent to http://example.com:3000. They ignore the path’s semantics and treat it as an opaque prefix. They are indifferent to scheme except insofar as the Secure flag restricts transmission to secure contexts. An origin boundary that holds for fetch and the DOM holds nowhere for cookies, which is why session identity needs separate treatment from the document isolation described in origin computation and the same-origin policy.
Set-Cookie: session=8f3c...; Path=/; HttpOnly; Secure; SameSite=Lax
Set-Cookie: theme=dark; Path=/; Max-Age=31536000; SameSite=Lax
Set-Cookie: __Host-id=9a1b...; Path=/; Secure; HttpOnly; SameSite=Strict
SameSite needs a definition of site, which is coarser than origin
SameSite compares the site of the request’s initiator with the site of the request’s target. A site is the scheme plus the registrable domain — the public suffix plus one label — so https://shop.example.com and https://api.example.com are the same site, while http://shop.example.com and https://shop.example.com are not, because the comparison is schemeful. The registrable domain comes from the Public Suffix List, which is why a.github.io and b.github.io are different sites even though both are subdomains of github.io: the list marks github.io as a suffix under which independent parties register.
The three values behave differently in ways that are easy to state precisely and easy to misread:
Strictsends the cookie only when the initiator and target are same-site. A link from an external page to aStrictpage arrives without the cookie, so a logged-in user following an emailed link appears logged out until they navigate again within the site.Laxsends the cookie on same-site requests and on cross-site top-level navigations whose method is safe. A cross-site link click or a typed URL carries the cookie; a cross-site formPOST, an image load, aniframe, and afetchdo not.Nonesends the cookie on every request, and is only accepted alongsideSecure. Without that pairing, a cookie set over plain HTTP could be attached to a cross-site request from an insecure context, which is why browsers reject the combination.
When no SameSite attribute is present, browsers apply Lax by default. The default is not in RFC 6265, which specifies no such behaviour; it is a browser intervention now codified in the draft revision of the specification. Chromium keeps a deliberate hole: for a cookie created within roughly the last two minutes, a cross-site top-level POST may still receive it. This exists so that a server can set a cookie and immediately redirect through a cross-site identity provider without breaking the flow. It is not a guarantee and should not be designed around.
Lax is often described as CSRF protection. It blocks the classic cross-site form POST carrying a session cookie, and that is a large category. It does not block a state-changing GET reached by a top-level navigation, because that navigation is exactly the case Lax permits. It does not block requests from a same-site but attacker-controlled subdomain, because they are same-site by construction. And it does not block requests from a different port or a different path on the same host. The defence is a reduction in attack surface, not a proof of origin, and the residual cases are the subject of CSRF defences and the SameSite interaction.
Prefixes encode the constraints the server cannot verify
Because the cookie specification lets a server set attributes that weaken a cookie, the __Host- and __Secure- name prefixes let a cookie declare invariants that the browser enforces at set time. A cookie named __Secure- must be set with Secure over a secure connection. A cookie named __Host- must additionally have Path=/ and no Domain attribute, making it host-only and available at every path.
The prefixes are useful because they defend against a specific failure: an attacker who can write a cookie on a subdomain — through a compromised application, a stale DNS entry, or an open redirect that reflects a Set-Cookie — cannot overwrite a __Host- cookie on the parent, because the parent’s name is host-only and the subdomain’s Domain=example.com variant fails the prefix check. Without the prefix, the two cookies coexist, both are sent, and path length and then creation time decide which the server reads first.
HttpOnly prevents document.cookie from reading a cookie, and from replacing it, though not from setting a differently scoped cookie with the same name. It is the only defence against script access, and it does not help if the script can issue requests: an XSS payload that cannot read the session cookie can still make authenticated calls from the victim’s browser. Secure restricts transmission to secure schemes and, in current browsers, causes a Secure cookie set over plain HTTP to be rejected rather than silently downgraded.
Where a shared parent domain breaks the model
The most common structural mistake is putting unrelated applications on subdomains of one registrable domain and setting cookies on the parent. Every application under example.com then receives the cookie, and every application that can be induced to set a cookie on example.com can influence what the others read.
Three concrete failure paths follow:
Subdomain takeover. If old.example.com has a dangling DNS record pointing at a deprovisioned hosting bucket, an attacker can serve content there. That origin is not same-origin with the main application, so it cannot read the main page. But it is on the same site, and if any cookie is scoped to Domain=example.com, the attacker’s page receives it on its own requests and can read others’ cookies via document.cookie unless they are HttpOnly. HttpOnly limits the damage to requests the attacker can trigger, which is still a session riding capability.
Cookie tossing and shadowing. A subdomain that sets Set-Cookie: session=x; Domain=example.com; Path=/ creates a second cookie with the same name. Both are sent. RFC 6265 orders by path length, and the two are equal here, so order is by creation time, oldest first. A server that takes the first value, or that concatenates values, can be made to read attacker-chosen state. Servers should reject duplicate names rather than pick one.
Scope confusion across schemes. Cookies have no scheme component, so a session cookie set by https://example.com is also sent to http://example.com if such a URL is reachable, and a network attacker on plain HTTP can read the cookie from the request and can inject a response carrying a Set-Cookie for a path the application reads. The Secure flag plus HSTS closes the plain-HTTP path; __Host- ensures the cookie cannot be shadowed by a wider one.
Partitioning and the browser-side retreat
The third-party cookie — set in a top-level page’s context but scoped to an embedded iframe’s host — is blocked outright in Safari and partitioned by top-level site in Firefox. In Chromium it survives, but the Partitioned attribute, which requires Secure, lets a cookie be keyed by the top-level site in addition to host and path. The same embedded origin then receives a different cookie jar in each top-level site, so it cannot use a cookie as a cross-site identifier for the user. The Storage Access API lets an embedded origin that has been granted user interaction request unpartitioned access, which restores the old behaviour for genuine login flows.
For the application, the practical consequences are that a cookie can no longer be assumed to identify the same session in every embedding context, and that a login inside an iframe requires either partitioning and a per-embedder session or a top-level popup. Cookies remain the main credential the browser attaches automatically to cross-site requests, which keeps them the primary CSRF surface even as their scope narrows.
References
- RFC 6265: HTTP State Management Mechanism — IETF
- Cookies: HTTP State Management Mechanism (draft-ietf-httpbis-rfc6265bis) — IETF HTTP Working Group
- HTML Standard — Same site — WHATWG