#10806·better-auth

feat(cookies): support the `__Host-` cookie prefix

Author: newcomerCreated Aug 14, 2026Updated Sep 17, 2026
Labelstarget: patchcoresecurity

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.tsHOST_COOKIE_PREFIX = "__Host-" is declared, and stripSecureCookiePrefix strips either prefix.
  • cookies/index.ts:71 — the only emit path, hardcoded to the other constant:
    typescript
    const secureCookiePrefix = secure ? SECURE_COOKIE_PREFIX : "";
  • cookies/index.ts imports HOST_COOKIE_PREFIX solely 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 extend useSecureCookies to accept "host".
  • Throw when combined with advanced.crossSubDomainCookies.enabled — mutually exclusive by definition, in the style of the existing baseURL is required when crossSubdomainCookies are enabled check.
  • Reject a path override in advanced.cookies.*.attributes / defaultCookieAttributes, since __Host- requires Path=/.

Two things that make this more than a one-liner

  1. 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.
  2. Other packages reconstruct the prefix. packages/electron/src/cookies.ts and packages/expo/src/client.ts both 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.