feat(web): roll out checkSameOrigin CSRF guard to all cookie-auth mutation routes#9
Merged
Merged
Conversation
…ation routes
Extends the same-origin CSRF guard (previously only on change-password and
device/approve) to every remaining cookie-authenticated, state-changing API
route. checkSameOrigin runs as the first statement in each handler — before
auth, body parsing, rate limiting, DB access, or any side effect — so a
cross-origin request is rejected with 403 before doing any work. This is
defense-in-depth on top of the SameSite=Lax session cookie.
Guarded handlers (17): auth/login, auth/logout, policies POST, policies/[id]
PATCH/PUT/DELETE, policies/load-starters POST, webhooks POST, webhooks/[id]
PATCH/DELETE, webhooks/[id]/test POST, tokens/[id] DELETE, users POST,
budgets/[userId] PUT/DELETE, runs/[id]/decide POST, sessions/[id] PATCH.
login is included so a forged cross-origin sign-in (login-CSRF / session
fixation) is rejected before the rate-limiter or scrypt hash runs.
Deliberately NOT guarded, with rationale documented inline / by exclusion:
- runs/search POST is read-only (returns repos/branches filter options, writes
nothing) so it carries no CSRF risk.
- All /api/sdk/* routes are Bearer-only (cookies can't authenticate them, so
they're not CSRF-able).
- auth/device + auth/device/token are pre-auth device-grant endpoints that
never read the session cookie.
Adds csrf-guard.test.ts: 57 cases (19 cookie-auth mutation routes × {cross-
origin → 403, same-origin → passes guard, Origin-less → passes guard}),
covering the two pre-existing guards too so the file is the authoritative
cross-origin-rejection regression gate.
Verification: web lint 0/0, full build green, 1223 tests pass. An adversarial
3-lens review (completeness / ordering / scope) returned 0 real problems —
no missing route, no side-effect-before-guard.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Extends the same-origin CSRF guard — previously only on
change-passwordanddevice/approve— to every remaining cookie-authenticated, state-changing API route.checkSameOrigin(req)runs as the first statement in each handler (before auth, body parsing, rate limiting, DB access, or any side effect), so a cross-origin request is rejected with403before any work happens. Defense-in-depth on top of theSameSite=Laxsession cookie.Guarded handlers (17 new)
auth/login,auth/logout,policiesPOST,policies/[id]PATCH/PUT/DELETE,policies/load-startersPOST,webhooksPOST,webhooks/[id]PATCH/DELETE,webhooks/[id]/testPOST,tokens/[id]DELETE,usersPOST,budgets/[userId]PUT/DELETE,runs/[id]/decidePOST,sessions/[id]PATCH.loginis included so a forged cross-origin sign-in (login-CSRF / session fixation) is rejected before the rate-limiter or scrypt hash runs.Deliberately NOT guarded (rationale)
runs/searchPOST/api/sdk/*auth/device,auth/device/tokenTests
New
csrf-guard.test.ts— 57 cases (19 cookie-auth mutation routes × {cross-origin →403, same-origin → passes guard, Origin-less → passes guard}). Covers the two pre-existing guards too, making this file the authoritative cross-origin-rejection regression gate.Verification
npm run buildgreen, 1223 tests passruns/searchexclusion andlogininclusion both validated.Independent of #8 (different files; no overlap).
🤖 Generated with Claude Code