Loading Cookie Attribute Analyser…
A Set-Cookie header is a name, a value, and a run of semicolon-separated attributes that decide who the cookie is sent to, how long it survives, and whether script can read it. The browser treats the value as opaque bytes; everything worth arguing about is in the attributes after it.
Three of them carry nearly all the protection. Secure withholds the cookie from any request that is not over HTTPS. HttpOnly hides it from document.cookie, so a cross-site scripting bug anywhere on the origin cannot exfiltrate it in a single line. SameSite decides whether it rides along on requests that started at another site. A session cookie missing any of the three is worth a second look, and one missing all three is a session waiting to be borrowed.
Nearly every cookie attribute is advice a server gives itself. The name prefixes are different, because the browser checks them and refuses to store anything that fails. A name beginning __Secure- is only stored when the Secure attribute is present. A name beginning __Host- is only stored when it is Secure, carries Path=/, and has no Domain attribute at all.
That last combination pins the cookie to one exact host. No sibling subdomain can set it, receive it, or overwrite it with a value of its own choosing — which matters because subdomains are frequently delegated, occasionally abandoned, and sometimes claimable by whoever notices the dangling DNS record first. This site’s owner session uses __Host- for that reason, and the failure mode to know about is silence: get the shape wrong and the cookie simply never appears.
Lax sends the cookie on a top-level navigation using a safe method, so an ordinary link from another site still works, and withholds it from cross-site form posts, frames, images and background fetches. Strict withholds it from the link as well, which means somebody arriving from a search result or an email lands looking signed out and has to navigate a second time. None sends it everywhere and is only accepted alongside Secure.
RFC 6265bis makes Lax the default and Chrome enforces that, but engines have arrived at it at different times, so leaving the attribute off means a cross-site behaviour that varies by browser. Writing the value you actually intend removes a difference you would otherwise learn about from a bug report.
Max-Age is a count of seconds and Expires is a date; where both appear, Max-Age wins. With neither, the cookie lasts as long as the browser considers the session to be running, which on a phone restoring its tabs can be a great deal longer than you assume.
Long lifetimes are worth resisting, because a stolen cookie stays valid for exactly as long as it says, and cookies are stolen by being copied. Chromium caps any expiry at 400 days regardless of what was requested, so a ten-year Max-Age is not honoured anyway — it just advertises an intention. Bounding the session on the server, as this site does at eight hours, is the part that genuinely limits the damage.
It stops the value being read by script, which closes the easy exfiltration. It does not stop an injected script using the session in place: the cookie still rides along on requests the attacker’s code makes from your own page.
Usually not. Omitting it produces a host-only cookie that goes to the exact host that set it. Adding it hands the cookie to every subdomain, and lets any of them overwrite it, because cookies have no origin integrity to speak of.
The two usual causes are SameSite=None without Secure, and a __Host- name carrying a Domain attribute or a path other than the root. Both make the browser discard the whole header silently, which presents as a cookie that mysteriously never exists.