diff --git a/docs/config/env.md b/docs/config/env.md index 5a300ded..d8bb5d14 100644 --- a/docs/config/env.md +++ b/docs/config/env.md @@ -62,6 +62,14 @@ OPAQUE (RFC 9807) is a zero-knowledge password-authenticated key exchange: the p | `OXICLOUD_AUTH_OPAQUE_KSF_ITERATIONS` | `1` | Client-side Argon2id iteration count (OWASP interactive-auth recommendation). | | `OXICLOUD_AUTH_OPAQUE_KSF_PARALLELISM` | `1` | Client-side Argon2id parallelism lanes (OWASP recommendation). Higher only helps on multi-core hardware and can hurt single-core / older mobile devices. | +### DPoP — session cookie binding (RFC 9449) + +DPoP cryptographically binds a session cookie to a browser-held ECDSA keypair (P-256, non-extractable via `SubtleCrypto`). Every request carries a signed proof the middleware verifies against the session's binding. Closes the info-stealer replay threat: a cookie copied to another machine is useless without the private key. Non-browser clients (Nextcloud sync via app passwords, CLI via device-authorization) never bind and are exempted at the middleware regardless of mode. See `docs/plan/dpop.md` for the full rollout plan. + +| Variable | Default | Description | +|---|---|---| +| `OXICLOUD_DPOP_MODE` | `off` | Enforcement mode. `off` = middleware pass-through (default, ship-safe). `opportunistic` = verify when a proof is present, log `dpop.header_missing_but_session_bound` audit when absent on a bound session, but allow the request through (rollout mode — catches client bugs). `required` = bound sessions MUST present a valid proof or 401. Unbound sessions always exempt. Recommended rollout: `off` → `opportunistic` for 2-4 weeks → `required`. For DPoP to be meaningful, cookies must be `Secure` (`OXICLOUD_COOKIE_SECURE=true` in production over HTTPS) and reverse-proxy `X-Forwarded-Proto` / `X-Forwarded-Host` must reach the app so the `htu` claim canonicalises correctly. | + ### Rate Limiting & Account Lockout | Variable | Default | Description | diff --git a/example.env b/example.env index 3ac30d60..e7c86ee4 100644 --- a/example.env +++ b/example.env @@ -250,6 +250,46 @@ DATABASE_URL=postgres://postgres:postgres@localhost:5432/oxicloud # helps on multi-core devices and hurts single-core / older mobile) #OXICLOUD_AUTH_OPAQUE_KSF_PARALLELISM=1 +# ----------------------------------------------------------------------------- +# DPOP — session cookie binding to a browser-held keypair (RFC 9449) +# ----------------------------------------------------------------------------- +# Each SPA session generates a P-256 ECDSA keypair with `extractable: false` +# in the browser at login time; the JWK thumbprint is sent as `dpop_jkt` +# and stored on the session row. Every subsequent request carries a signed +# DPoP proof header the middleware verifies against the session's binding. +# +# Threat closed: an info-stealer that copies the cookie to another machine +# cannot replay the session — the private key never leaves the browser +# (SubtleCrypto stores it in the crypto subsystem; JS can only call +# `sign()` on the handle, never `exportKey()`). +# +# Non-browser clients (Nextcloud sync, mobile apps via Basic Auth on app +# passwords, CLI via device-authorization) never bind a keypair — their +# session rows have `dpop_jkt IS NULL` and the middleware exempts them +# regardless of mode. So enabling this does NOT break your NC clients. +# +# Values: off | opportunistic | required +# off — middleware pass-through, no verification. Default. Ship- +# safe while the client rollout catches up. +# opportunistic — verify when a proof is present, reject invalid ones; +# allow when absent (log a warning if the session was +# bound). Rollout mode — catches client bugs before +# flipping enforcement. +# required — bound sessions MUST present a valid proof or 401. +# Unbound sessions still work (see NC-client note above). +# +# Recommended rollout: off → opportunistic (2-4 weeks, watch audit for +# `dpop.header_missing_but_session_bound` counts trending to zero) → +# required. See `docs/plan/dpop.md` for the full rollout plan. +# +# NB: for DPoP to be meaningful, cookies must be `Secure` (HTTPS) — +# there's no point cryptographically binding a session that ships over +# plain HTTP. Set `OXICLOUD_COOKIE_SECURE=true` in production. Also +# ensure `X-Forwarded-Proto` + `X-Forwarded-Host` reach the app if +# you're behind a reverse proxy — the middleware reads those to build +# the canonical `htu` claim the proof binds to. +#OXICLOUD_DPOP_MODE=off + # ----------------------------------------------------------------------------- # RATE LIMITING & ACCOUNT LOCKOUT # -----------------------------------------------------------------------------