# Cookie attributes that protect less than you think

> Secure, HttpOnly, SameSite, Domain, Path and the __Host- prefix each guard against one thing only. Here is what each really does and where people misjudge it.

Published 2026-10-27 · https://aiskills402.com/blog/cookie-attributes-mistakes

A login cookie is only as safe as the attributes in its header, and each attribute closes exactly one door. Secure keeps the cookie off plain HTTP, HttpOnly keeps it away from page scripts, SameSite limits which cross-site requests carry it, and the cookie's scope decides which hosts receive it. People get into trouble by assuming one attribute covers another's job. The rest of this guide goes through the attributes one at a time, says what each protects and what it leaves open, and ends with a check you can run on your own responses.

## Secure: the one that is not implied by HTTPS

Secure tells the browser to send the cookie only over an encrypted connection. Many teams reason that the site is HTTPS-only, so the attribute is redundant. OWASP says the opposite: forcing HTTPS everywhere does not help if the cookie lacks Secure, because a browser can be tricked into sending it over an unencrypted request to the same host, for example when an attacker on the network injects a plain-HTTP request ([OWASP Session Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html)).

Two limits are worth knowing. First, Secure does not hide the value from anything on the user's machine: MDN notes that such cookies can still be read from the disk, and from script when HttpOnly is absent ([MDN Set-Cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie)). Second, a page served over plain HTTP cannot set a cookie with this attribute at all, except on localhost, which is why a flag that works in development can quietly disappear behind a proxy that terminates TLS and hands the app an http scheme.

## HttpOnly: stops reading, not using

HttpOnly removes the cookie from `document.cookie`, so an injected script cannot copy a session id and send it away. That is a real gain, and OWASP calls the attribute mandatory for session identifiers. The common mistake is to treat it as protection against cross-site scripting as a whole. MDN is plain about the gap: a cookie marked this way is still attached to requests that the page's own script makes with `fetch` or XMLHttpRequest. An attacker who can run script on your page does not need to read the cookie to act as the user; the browser will add it for them. HttpOnly narrows what a script injection can steal, and it does nothing about what the script can do while it is running.

The opposite mistake is just as common in review comments: demanding HttpOnly on every cookie. A theme, a language or a layout choice that your own front-end code reads has no reason to carry the flag. Marking it would break the feature and protect nothing.

## SameSite: a CSRF layer, with a None trap

SameSite decides whether a cookie travels with requests that start on another site. Strict sends it only from the same site. Lax also allows top-level navigations with safe methods, such as a click on a link, and leaves out requests made by `fetch`, images, scripts and frames. None sends it everywhere, and MDN requires Secure alongside it.

Three misjudgements follow. The first is leaving the attribute out and counting on the browser default: MDN says only that some browsers apply Lax when it is missing, and that the Lax default is a softer variant which still sends the cookie on a POST made within two minutes of setting it. The second is believing SameSite replaces anti-forgery tokens. OWASP treats it as defence in depth and states explicitly that it is not a replacement for a CSRF token. The third is setting None without Secure, which browsers that follow the rule will refuse to store, so the login simply fails for some users and works for others, depending on version. Set the value explicitly on every session cookie, and use None only where a genuine cross-site flow, such as an embedded widget, needs it.

## Domain and Path: scope, not security

Leaving Domain off is the safer choice, and the surprise is that adding it widens the audience. Without it, only the issuing host gets the cookie back. With it, MDN says every subdomain of the named domain receives the cookie as well, and a leading dot is ignored. OWASP explains the consequence: a flaw on one subdomain, or an attacker-controlled one, can read or overwrite the session identifier of a more sensitive sibling, and it can plant a chosen identifier for a session fixation attack. The usual cause is a team that set `Domain=example.com` once so that a marketing site and an app could share a login, then forgot the setting when the shared login was dropped.

Path gets mistaken for a wall. MDN states that the attribute is not a security measure, and a script on a different path can still read the cookie, since any page on the same origin can reach it through the DOM. Use it to keep requests small, not to separate two applications. If two applications of different trust live on one host, move them to separate hosts.

## The __Host- prefix: the strict combination

A cookie whose name starts with `__Host-` is accepted only if it is Secure, set from a secure page, has no Domain and has `Path=/`. MDN describes the result as a cookie that no other host on the domain ever receives, that no narrower path can shadow, and that comes as close as cookies get to treating the origin as the boundary. OWASP recommends the prefix for session identifiers because it blocks subdomain forgery and downgrade attacks.

The usual mistake is writing the name with the prefix and then adding `Domain=example.com` out of habit, or `Path=/app`. The browser does not complain loudly; it rejects the header and the cookie never exists, so the symptom is a login that cannot persist. A related prefix, `__Secure-`, needs only Secure and suits cases where subdomains must share the cookie. A working header looks like this:

```
Set-Cookie: __Host-sid=abc123; Secure; HttpOnly; SameSite=Lax; Path=/
```

## Expiry: how long a stolen cookie works

Without Expires or Max-Age the browser treats the cookie as temporary and drops it when it closes, but MDN warns that session restore features bring such cookies back. With a lifetime, Max-Age wins over Expires when both are present. MDN gives no recommended duration, so the length is your policy. OWASP asks for an absolute timeout on every session regardless of activity, and offers working ranges of roughly 4 to 8 hours for an office application used all day, and idle timeouts of 15 to 30 minutes for low-risk systems ([OWASP Session Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html)). A cookie that lives for months turns every leaked copy into a long-lived key. Note also that an idle timeout limits guessing, but a hijacker who keeps sending requests keeps the session alive, which is why the absolute limit matters more.

One more detail: the cookie's lifetime and the server's session lifetime are separate. A short Max-Age on a session the server never invalidates only hides the problem from the browser.

## A check that can fail

Do not review cookie attributes by reading the code that sets them; read what the server actually sends. Frameworks, proxies and CDNs rewrite headers, so the application source may say one thing and the wire another.

1. Request your login page and a post-login page with `curl -si` and collect every `Set-Cookie` line.
2. Mark which cookies carry the login. Judge those strictly and leave theme or language cookies alone.
3. For each login cookie, ask in turn: is Secure there, is HttpOnly there, is SameSite stated, is Domain absent unless a sibling host truly needs the cookie, and is the lifetime something you chose.
4. For any name starting with `__Host-`, confirm there is no Domain and the path is the root.
5. Now make the test capable of failing. Temporarily drop one attribute in a staging build and run the same check again. If it still passes, your check reads the wrong thing, perhaps the application config instead of the response.

If you already have a consent banner, remember that it answers whether a cookie may exist, which is a separate question from how it is protected; our note on [cookieless analytics and EU consent](/blog/cookieless-analytics-eu-consent) covers that side.

## When this does not apply

Only cookies that carry a login are in scope here. A site with no accounts, or one that keeps its session in a header-based token, has a different set of risks, and some of the advice here is moot. Browsers also differ and change: MDN itself leaves the default SameSite behaviour vague and the older third-party cookie rules are being phased out at different speeds, so test on the browsers your users run. Attributes cannot fix a missing server-side session check, a predictable identifier or a cross-site scripting hole; they only reduce what an attacker gains from one. We did not run browser experiments for this guide. It rests on the two public documents linked above, read on 27 October 2026, and either may have changed since you read this.

## Checking it automatically

If you want the header check done for you, our [cookie-attributes-review](https://aiskills402.com/skills/cookie-attributes-review) skill reads your response headers or the cookie-setting source and gives back a brief list of the attribute slips that put a login at risk, or says plainly that it found none. It deliberately stays quiet about harmless look-alikes such as a script-readable language cookie. You can find it, and the other skills we write, in the store at https://aiskills402.com.
