etherpad-lite/src/node/security
John McLear 682cb65625
security(oidc): stop shipping a hardcoded cookie key and reflecting all CORS origins (#8070)
* security(oidc): stop shipping a hardcoded OIDC cookie key and reflecting all CORS origins

The embedded OIDC provider signed its interaction/session/grant cookies
with the committed literal key `['oidc']`, so anyone with the public
source could forge valid `.sig` cookies (defeating the provider's
cookie-integrity boundary), and `clientBasedCORS` returned `true` for
every origin, reflecting arbitrary `Origin` values into
`Access-Control-Allow-Origin` on the token/userinfo endpoints.

Adds OidcProviderSecurity.ts with two unit-tested pure helpers:

- resolveOidcCookieKeys(): prefers an operator-supplied
  `settings.sso.cookieKeys` (ordered array for rotation), otherwise
  derives a secret key from the persisted session secret via a
  domain-separated SHA-256 — stable across restarts and multi-pod, never
  committed to source; falls back to an ephemeral random key.
- isOriginAllowedForOidcClient(): allows a CORS origin only when it
  matches the origin of one of the client's registered redirect URIs.

Verified end-to-end against a real oidc-provider@9.9.1 instance
(cookies.keys accepted, Client.redirectUris read correctly, attacker
origin blocked). Reported privately by meifukun.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* security(oidc): use DB-backed SecretRotator for cookie keys under default rotation config

Addresses Qodo review on #8070. With Etherpad's default cookie settings
(keyRotationInterval + sessionLifetime set), key rotation is enabled and
`settings.sessionKey` stays null unless SESSIONKEY.txt is provisioned, so
the previous fallback handed the OIDC provider a per-process random key —
breaking in-flight OIDC cookies on restart and across horizontally-scaled
pods on a default install.

resolveOidcCookieKeys() now takes an optional rotatedSecrets array
(priority: operator cookieKeys > rotated secrets > session-key
derivation > random) and returns it by reference so a live rotation
propagates to keygrip. OAuth2Provider.expressCreateServer() creates a
dedicated `oidcCookieSecrets` SecretRotator — the same DB-backed
mechanism the Express session cookies use — when the operator hasn't
pinned settings.sso.cookieKeys and rotation is enabled.

Adds unit tests for the rotatedSecrets priority/by-reference behavior and
an integration test proving the default config yields stable DB-backed
secrets rather than a random key.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(settings): give sso.cookieKeys a real key in the template

The doc block sat above `sso.clients` with no `cookieKeys` property, so the admin
UI's template-comment extractor (which attaches leading comments to the next
property node) would have shown it as documentation for `clients`. An unset
OIDC_COOKIE_KEY yields [""], which resolveOidcCookieKeys() filters out, so the
derived/rotated key path is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: SamTV12345 <40429738+samtv12345@users.noreply.github.com>
2026-07-27 20:36:27 +02:00
..
crypto.ts Added typescript to etherpad 2024-02-05 21:13:02 +01:00
OAuth2Provider.ts security(oidc): stop shipping a hardcoded cookie key and reflecting all CORS origins (#8070) 2026-07-27 20:36:27 +02:00
OAuth2User.ts Enforce 2-space indentation across codebase (#7426) 2026-04-01 19:48:45 +01:00
OIDCAdapter.ts fix(oidc): fix OIDCAdapter broken flows (#7837) 2026-05-25 16:39:21 +02:00
OidcProviderSecurity.ts security(oidc): stop shipping a hardcoded cookie key and reflecting all CORS origins (#8070) 2026-07-27 20:36:27 +02:00
SecretRotator.ts Added typescript to etherpad 2024-02-05 21:13:02 +01:00