Browsers apply special rules to cookies whose names begin with __Host- or __Secure-. A session cookie with a plain name like session or sid gets none of that protection. SetEnforce flags the gap when a response sets a cookie that looks like a session identifier and the name does not use one of these prefixes. This article explains what the finding means, how the scanner reaches it, and how to fix it.
What the prefixes do
The prefixes are defined in section 4.1.3 of the HTTP State Management Mechanism draft (rfc6265bis). Compliant browsers enforce them when a Set-Cookie header arrives.
__Secure-: the browser accepts the cookie only if it is set with theSecureattribute from a secure (HTTPS) origin.__Host-: it needs everything__Secure-needs, and the cookie must also havePath=/and noDomainattribute. That binds the cookie to the exact host that set it, so subdomains cannot share it.
If a response violates these rules, the browser discards the cookie. A misconfiguration fails visibly instead of silently weakening the session.
Why a plain name is a weakness
Cookie attributes protect a cookie after it is stored. They say nothing about who can write it. Browsers do not authenticate the origin of a cookie write, so a sibling subdomain, or an attacker on the network, can often set a cookie that the main application will read.
That opens three attacks:
- Cookie tossing. An attacker who controls any subdomain (a forgotten staging host, a user-content site, a subdomain takeover) sets
session=<attacker value>; Domain=example.com. The browser sends both the legitimate and the planted cookie. Depending on ordering, the application may accept the attacker's value, which enables session fixation or login CSRF. - Downgrade overwrite. If a user briefly loads an
http://URL, an active attacker on the network path can inject aSet-Cookieresponse that overwrites the stored session cookie. ASecureattribute alone does not stop this in all browsers, because the older rules let insecure origins overwrite Secure cookies. The prefix rules close that hole. - Scope creep. A session cookie accidentally scoped with
Domain=example.comis sent to every subdomain, including ones that run less trusted code.
The prefix turns these conventions into guarantees the browser enforces. A __Host- cookie cannot be set from a subdomain or over cleartext, so the application knows any cookie with that name came from itself over HTTPS.
Why it is rated low
SetEnforce rates this finding low because it is a defense-in-depth gap, not a direct exposure. A session cookie without a prefix cannot be read or stolen because of that alone. Exploiting the missing prefix usually needs a prior foothold: control of a sibling subdomain, or an active network position combined with a user visiting a cleartext URL. If the cookie is also missing Secure, HttpOnly or SameSite, those separate findings carry more weight.
The cost is real but indirect. A successful tossing or fixation attack means account takeover, which puts you in breach-notification territory. Auditors working from OWASP session-management guidance and similar control frameworks expect prefixes on the session cookies of any modern application, so a missing prefix is an easy item for them to cite.
How the scanner detects and rates it
During the web checks, SetEnforce requests the domain's main page and follows redirects to the final HTTPS response. It collects every Set-Cookie header from the chain and classifies each cookie.
A cookie counts as session-like if its name matches a common session pattern (session, sid, PHPSESSID, JSESSIONID, connect.sid, auth, token and similar), or if it is HttpOnly and has no explicit lifetime. For each session-like cookie, the scanner checks whether the name starts with __Host- or __Secure-, and whether the attributes satisfy that prefix.
The finding is raised at severity low when at least one session-like cookie has no prefix. The evidence lists the cookie name, the attributes observed (Secure, HttpOnly, SameSite, Domain, Path), and the URL that set it. A cookie that carries a prefix but violates its rules, for example __Host-sid with a Domain attribute, is reported too, although a real browser would reject it. Non-session cookies such as analytics or language preferences are ignored.
The check passes when every session-like cookie observed uses __Host- or __Secure- and meets that prefix's requirements. It reports not tested if the site sets no cookies on unauthenticated pages, because the scanner never logs in.
How to fix it
1. Pick the right prefix
Use __Host- for a session cookie the application sets for its own host only, which covers almost every case. Use __Secure- only when the cookie must be shared across subdomains, for example single sign-on across app.example.com and api.example.com. Be aware that __Secure- leaves the cookie-tossing path from subdomains open.
2. Set the cookie correctly
A valid __Host- cookie looks like this:
Set-Cookie: __Host-sid=3f9c1e0a7b; Path=/; Secure; HttpOnly; SameSite=Lax
Do not add Domain, and do not use any Path other than /.
Framework examples:
# Flask
app.config.update(
SESSION_COOKIE_NAME="__Host-sid",
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE="Lax",
SESSION_COOKIE_PATH="/",
SESSION_COOKIE_DOMAIN=None,
)
// Express (express-session)
app.use(session({
name: '__Host-sid',
secret: process.env.SESSION_SECRET,
cookie: { secure: true, httpOnly: true, sameSite: 'lax', path: '/' },
}));
// php.ini
session.name = "__Host-PHPSESSID"
session.cookie_secure = 1
session.cookie_httponly = 1
session.cookie_samesite = "Lax"
If TLS terminates at a proxy, make sure the application knows the original scheme (for example through X-Forwarded-Proto). Otherwise frameworks may omit Secure, and the browser will drop the prefixed cookie.
3. Handle the rollout
Renaming a session cookie logs everyone out, because existing browsers hold the old name. Plan for that:
- Deploy during a low-traffic window, or accept both names for a short transition and issue only the new one.
- Expire the old cookie explicitly with
Set-Cookie: sid=; Max-Age=0; Path=/. - Update anything that references the name: load balancer stickiness rules, WAF rules, log parsers, and integration tests.
Confirming the fix on re-scan
Start by checking the header yourself:
curl -sI https://example.com/ | grep -i '^set-cookie'
Every session cookie should begin with __Host- or __Secure- and show the attributes above. Next, open browser developer tools, load the site, and confirm the cookie is present in storage. If it is missing, the browser rejected it. That usually means Secure was dropped behind a proxy or a Domain attribute is still being set.
Then trigger a new scan from the domain page. The finding should clear, and the cookie evidence should show the prefixed name with Secure, HttpOnly and SameSite present. If it persists, compare the evidence URL with the response you tested. A different endpoint, such as a login redirect or a CDN-served page, may set its own cookie.
Key takeaways
- The
__Host-and__Secure-prefixes make the browser enforceSecure(and, for__Host-, host-only scope withPath=/), which blocks cookie tossing and downgrade overwrites. - A missing prefix is a low-severity hardening gap, not a direct leak, but it makes session fixation and takeover easier for an attacker with a foothold.
- Prefer
__Host-for session cookies. Use__Secure-only when subdomains must share the cookie. - Renaming logs users out, so plan the transition and clean up the old cookie.
- Verify with
curland browser tools, then re-scan to confirm the finding clears.