Files
pad/docs
xarmian d4e69a566b fix(auth): reset-password, register and bootstrap refuse a cross-site form, as login does (BUG-3680) (#2146)
* fix(auth): reset-password, register and bootstrap refuse a cross-site form, as login does (BUG-3680)

/api/v1/auth/reset-password is CSRF-exempt and a completed reset sets a
web session cookie. decodeJSON does not check the content type, so a
cross-site text/plain form whose name=value serializes as JSON could
complete a reset of the attacker's own account in the victim's browser
and sign the victim into it (login CSRF). Only login and 2FA login-verify
called refuseCrossSiteSignIn (BUG-3350).

The population, by every handler that creates a session:
- login, 2fa/login-verify: guarded already.
- reset-password: unguarded. Guarded now.
- register: CSRF-exempt without a session, and an anonymous signup signs
  the browser into the new account. Same class. Guarded now.
- bootstrap: CSRF-exempt and signs the browser into the admin it creates.
  On a fresh instance reachable by loopback, or in open-bootstrap mode,
  a cross-site form could plant the attacker's admin. Guarded now.
- oauth-login: requires the sidecar's cloud_secret, so not browser-
  reachable. Left as is.
- rotateSessionsAfterCredentialChange callers are authenticated,
  CSRF-checked writes; the CLI approval session is not a browser cookie.

Every legitimate client of the three already sends application/json with
no Origin (CLI) or this server's origin (the SPA), and pad-cloud calls
none of them.

Tests, one per door: text/plain, urlencoded, foreign Origin and null
Origin are refused, set no session cookie and change nothing (the reset
token survives and then completes; no account is created; no admin is
created). Same-origin and headless JSON still complete. Removing one
door's guard fails exactly that door's test. TestBUG3350's login cases
still pass.

docs/api.md names the refusal for all five sign-in doors.

Claude-Session: https://claude.ai/code/session_01Rj4CMyK2EqxerMihbGs3Kr

* test(auth): the cross-site sign-in guard keeps an allowed CORS origin, a remote token bootstrap and an admin's SPA register working (BUG-3680)

Codex r2 on #2146 found the new tests did not exercise these legitimate
callers, so a guard that broke them could pass. Each control is paired with a
refusal on the same door, so it proves the guard's discrimination and not a
door that ignores Origin. Mutants: dropping the CORS list fails the CORS test;
requiring an Origin fails the remote token bootstrap.

Claude-Session: https://claude.ai/code/session_01Rj4CMyK2EqxerMihbGs3Kr
2026-10-14 03:30:20 -04:00
..