fix(auth): host-scope the OP session cookie by default; opt-in for cross-subdomain sharing #283

Merged
YousifShkara merged 1 commit from fix/BUNYIP-266-host-only-op-session-cookie into main 2026-06-30 06:01:59 +02:00
Owner

BUNYIP-266: when COOKIE_DOMAIN is set to a parent (e.g. .a8n.systems on c-01, .psa.systems on nc-01), the bunyip_op_session cookie is sent to every sibling subdomain (mailpit, infisical, mokosh-apps, drillmark) on every request, widening the steal surface. The audit's recommendation is host-only by default with an explicit opt-in for the rare deployment that genuinely needs cross-subdomain sharing.

Adds cookie_shared_domain: bool (env BUNYIP_COOKIE_SHARED_DOMAIN, default false) to Config, plus a Config::op_session_cookie_domain() helper that returns Some(cookie_domain) only when the flag is set. The cookie is then host-only by default regardless of how COOKIE_DOMAIN is configured. The hub access/refresh cookies still honour cookie_domain for now; tightening those is a separate ticket.

A startup tracing::warn! fires when COOKIE_DOMAIN is set without BUNYIP_COOKIE_SHARED_DOMAIN=true so an operator who relied on the previous default is notified before the next login flips behaviour.

The c-01/nc-01 staging/prod deployments set BUNYIP_COOKIE_SHARED_DOMAIN=true in a sibling docker-repo PR to preserve the current behaviour; flipping it to host-only is a deliberate operator change after they've audited which siblings (if any) actually read the cookie.

Existing user sessions are not invalidated: the old domain-scoped cookie keeps working until natural expiry (7 days), and the next login emits a host-scoped cookie. No forced logout.

The __Host- cookie-prefix half of the audit recommendation is deferred to a follow-up because renaming the cookie would force every active user through /login once; that's a UX hit this PR is structured to avoid.

#BUNYIP-266

BUNYIP-266: when `COOKIE_DOMAIN` is set to a parent (e.g. `.a8n.systems` on c-01, `.psa.systems` on nc-01), the `bunyip_op_session` cookie is sent to every sibling subdomain (mailpit, infisical, mokosh-apps, drillmark) on every request, widening the steal surface. The audit's recommendation is host-only by default with an explicit opt-in for the rare deployment that genuinely needs cross-subdomain sharing. Adds `cookie_shared_domain: bool` (env `BUNYIP_COOKIE_SHARED_DOMAIN`, default false) to `Config`, plus a `Config::op_session_cookie_domain()` helper that returns `Some(cookie_domain)` only when the flag is set. The cookie is then host-only by default regardless of how `COOKIE_DOMAIN` is configured. The hub access/refresh cookies still honour `cookie_domain` for now; tightening those is a separate ticket. A startup `tracing::warn!` fires when `COOKIE_DOMAIN` is set without `BUNYIP_COOKIE_SHARED_DOMAIN=true` so an operator who relied on the previous default is notified before the next login flips behaviour. The c-01/nc-01 staging/prod deployments set `BUNYIP_COOKIE_SHARED_DOMAIN=true` in a sibling docker-repo PR to preserve the current behaviour; flipping it to host-only is a deliberate operator change after they've audited which siblings (if any) actually read the cookie. Existing user sessions are not invalidated: the old domain-scoped cookie keeps working until natural expiry (7 days), and the next login emits a host-scoped cookie. No forced logout. The `__Host-` cookie-prefix half of the audit recommendation is deferred to a follow-up because renaming the cookie would force every active user through `/login` once; that's a UX hit this PR is structured to avoid. #BUNYIP-266
fix(auth): host-scope the OP session cookie by default; opt-in for cross-subdomain sharing
All checks were successful
E2E / Playwright against deployment (pull_request) Successful in 55s
Check / fmt + clippy + build + tests (pull_request) Successful in 10m59s
Create release / Create release from merged PR (pull_request) Has been skipped
04feaf099e
BUNYIP-266: when `COOKIE_DOMAIN` is set to a parent (e.g. `.a8n.systems` on c-01, `.psa.systems` on nc-01), the `bunyip_op_session` cookie is sent to every sibling subdomain (mailpit, infisical, mokosh-apps, drillmark) on every request, widening the steal surface. The audit's recommendation is host-only by default with an explicit opt-in for the rare deployment that genuinely needs cross-subdomain sharing.

Adds `cookie_shared_domain: bool` (env `BUNYIP_COOKIE_SHARED_DOMAIN`, default false) to `Config`, plus a `Config::op_session_cookie_domain()` helper that returns `Some(cookie_domain)` only when the flag is set. The cookie is then host-only by default regardless of how `COOKIE_DOMAIN` is configured. The hub access/refresh cookies still honour `cookie_domain` for now; tightening those is a separate ticket.

A startup `tracing::warn!` fires when `COOKIE_DOMAIN` is set without `BUNYIP_COOKIE_SHARED_DOMAIN=true` so an operator who relied on the previous default is notified before the next login flips behaviour.

The c-01/nc-01 staging/prod deployments set `BUNYIP_COOKIE_SHARED_DOMAIN=true` in a sibling docker-repo PR to preserve the current behaviour; flipping it to host-only is a deliberate operator change after they've audited which siblings (if any) actually read the cookie.

Existing user sessions are not invalidated: the old domain-scoped cookie keeps working until natural expiry (7 days), and the next login emits a host-scoped cookie. No forced logout.

The `__Host-` cookie-prefix half of the audit recommendation is deferred to a follow-up because renaming the cookie would force every active user through `/login` once; that's a UX hit this PR is structured to avoid.

#BUNYIP-266
YousifShkara deleted branch fix/BUNYIP-266-host-only-op-session-cookie 2026-06-30 06:01:59 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/bunyip!283
No description provided.