feat(cookies): support the `__Host-` cookie prefix
Summary
createCookieGetter can emit __Secure- but never __Host-. HOST_COOKIE_PREFIX is already declared in cookie-utils.ts and stripSecureCookiePrefix already handles it on the read side — only emission is missing.
Why
__Secure- stops a plaintext-HTTP write on the same host from overwriting a session cookie. It does not stop a related-domain write.
In a multi-tenant deployment where tenants are subdomains of one registrable domain (acme.example.com, globex.example.com), any sibling can set Domain=example.com — a compromised tenant, an XSS'd one, or a dangling DNS record in the zone. The browser then sends that cookie to every other sibling. Cookies carry no record of which host set them, so when two arrive under the same name the server cannot tell them apart, and the collision becomes cross-tenant session fixation.
__Host- closes this structurally: a cookie carrying a Domain attribute is not allowed to use a __Host- name, so an injected cookie can never collide with the real one. It is the standard mitigation for this topology, and tenant-per-subdomain is common enough to be worth supporting.
Current state
cookie-utils.ts—HOST_COOKIE_PREFIX = "__Host-"is declared, andstripSecureCookiePrefixstrips either prefix.cookies/index.ts:71— the only emit path, hardcoded to the other constant:const secureCookiePrefix = secure ? SECURE_COOKIE_PREFIX : "";cookies/index.tsimportsHOST_COOKIE_PREFIXsolely to re-export it.
Related: #4908 / #5172. The plugin docs used to say createAuthCookie "implements things like __secure prefix and __host prefix"; the fix was to delete the __host claim. So the gap is known — this issue asks to close it in the other direction.
What the defaults already give you
With crossSubDomainCookies disabled, createCookie already emits Secure, Path=/, and no Domain — exactly the three conditions __Host- requires. The prefix is the only missing piece.
Proposed shape
Happy to follow whatever you prefer:
advanced.useHostCookiePrefix?: boolean, or extenduseSecureCookiesto accept"host".- Throw when combined with
advanced.crossSubDomainCookies.enabled— mutually exclusive by definition, in the style of the existingbaseURL is required when crossSubdomainCookies are enabledcheck. - Reject a
pathoverride inadvanced.cookies.*.attributes/defaultCookieAttributes, since__Host-requiresPath=/.
Two things that make this more than a one-liner
- Existing sessions. Changing the prefix renames the cookie.
getSessionCookie(cookies/index.ts:484) falls back__Secure-${name}→${name}, so a deployment that switches to__Host-finds no existing cookie and signs every user out. Either document it as breaking and opt-in, or widen the chain to__Host-→__Secure-→ bare — though that fallback reintroduces exactly the collision the prefix exists to prevent, so the former seems right. - Other packages reconstruct the prefix.
packages/electron/src/cookies.tsandpackages/expo/src/client.tsboth build__Secure-names directly and would need the same branch.
Offer
Happy to open the PR if you'd like it. Tell me which option shape you want and how you'd prefer (1) to be handled, and I'll implement it with tests.
Source: better-auth/better-auth