A cookie’s scope is the set of requests that carry it. Domain, Path, Secure and SameSite each remove requests from that set, and the browser applies all of them before writing a Cookie header. Scope is the cheapest control in a web stack: free at request time, and it shrinks how often a session token crosses a network you do not own.
Convenience pushes the other way. A shared login across www, api and shop invites Domain=example.com; a single-page app under /app invites Path=/; a page that needs the session for one banner invites SameSite=None. Each widening is defensible alone, and each adds hosts or paths that can now read, replay or overwrite the token.
Domain is a suffix match, not a wildcard
RFC 6265 is blunt: with Domain=example.com the user agent sends the cookie to example.com, www.example.com and www.corp.example.com. A leading dot is ignored — “even though that character is not permitted” — so the dotted and undotted spellings are the same cookie. The dot never was a wildcard. The fixture shows the split: Python’s http.cookies keeps .example.com verbatim while the RFC’s domain-match algorithm strips it, so a server that keys scope logic on the raw header disagrees with the browser about the same cookie.
Omit Domain and you get a host-only cookie, returned “only to the origin server” — the narrow default, and the one you want. The wide version hands the session to every sibling subdomain, including ones nobody thinks of as the app: a status-page host, a docs host, a CMS marketing owns. The fixture’s table shows Domain=example.com attaching to evil.example.com, while a host-only cookie from www.example.com never reaches shop.example.com. A Domain attribute must domain-match the setting host, so widening goes upward only: Domain=shop.example.com from www.example.com is rejected.
Path is a prefix, not a directory
Path looks like a directory boundary and is not one. RFC 6265 defines path-match as three cases: identical paths, or the cookie path is a prefix ending in /, or the next character of the request path is /. Path=/admin matches /admin, /admin/ and /admin/users, but not /adminx or /administrator — the fixture walks that table. Path=/ is the default when the attribute is omitted, which is why “we set a path” often means nothing.
Path is not a security boundary, and the RFC says so: “the Path attribute cannot be relied upon for security”. The cookie still leaves the host on every matching request, and any script on an overlapping path reads it without HttpOnly. Treat Path as a request-count optimisation, never as an authorisation check.
Prefixes enforce what attributes cannot
Attributes are declarations; the two cookie-name prefixes are requirements the browser enforces at set time. __Secure- demands Secure and a secure origin. __Host- demands Secure, Path=/ and no Domain attribute, and the draft calls the result as close as a cookie can get to treating the origin as a security boundary. A __Host- cookie is host-only by construction, so no sibling subdomain can set, overwrite or read it.
That makes it a session-fixation defence, not a nicety. Fixation needs the attacker to choose the victim’s session id, and any host in the cookie’s domain can do that for a Domain-scoped cookie — as can a network attacker when Secure is missing, because a plaintext response can set a cookie the browser later sends over HTTPS. __Host-session=…; Path=/; Secure; HttpOnly; SameSite=Lax closes both.
When it breaks
The classic failure is the parent-domain session cookie, set because www and api both need it. It presents as a session token in the access log of a host that should never have seen it: a static-asset domain, a CDN, a compromised marketing subdomain. Diagnose by reading the Set-Cookie line on the login response and asking which hosts under that domain could set a matching cookie; if one should not read a session, Domain is the bug.
The second failure is fixation through a too-wide scope. An attacker who controls any host inside the cookie’s domain, or who can inject one plaintext response, plants a known session id before the victim logs in. The victim authenticates, the server binds that id to the account, and the attacker holds an authenticated session without ever seeing a credential. The blast radius is an account takeover with no authentication anomaly anywhere in the logs.
Check it quickly
Read the scope the server actually sets, then try to use it from a host that should not have it:
curl -sD - -o /dev/null https://app.example.com/login | grep -i '^set-cookie:'
curl -s -c /tmp/cj -o /dev/null https://app.example.com/login
grep -i 'session\|__Host' /tmp/cj
curl -sv -b /tmp/cj -o /dev/null https://shop.example.com/ 2>&1 | grep -i '^> cookie'
The first line should show a __Host- name with Secure, HttpOnly, SameSite=Lax and no Domain attribute, and the last should print nothing: a host-only cookie has no business on a sibling host. A Domain= in the first output, or a Cookie header in the last, is the leak: the same token is now readable by every host under that suffix.
Sources
- RFC 6265: HTTP State Management Mechanism, domain-match, path-match and the leading-dot rule, checked 2026-09-16.
- MDN: Set-Cookie, attributes and the
__Host-/__Secure-prefixes, checked 2026-09-16. - MDN: Using HTTP cookies, scope, host-only cookies and prefixes in practice, checked 2026-09-16.