Skip to content

feat(web): roll out checkSameOrigin CSRF guard to all cookie-auth mutation routes#9

Merged
iaj6 merged 1 commit into
mainfrom
feat/csrf-same-origin-rollout
Jun 5, 2026
Merged

feat(web): roll out checkSameOrigin CSRF guard to all cookie-auth mutation routes#9
iaj6 merged 1 commit into
mainfrom
feat/csrf-same-origin-rollout

Conversation

@iaj6

@iaj6 iaj6 commented Jun 5, 2026

Copy link
Copy Markdown
Owner

What

Extends the same-origin CSRF guard — previously only on change-password and device/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 with 403 before any work happens. Defense-in-depth on top of the SameSite=Lax session cookie.

Guarded handlers (17 new)

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 (rationale)

Route Why excluded
runs/search POST Read-only (returns repos/branches filter options, writes nothing) → no CSRF risk. Documented inline.
/api/sdk/* Bearer-only — cookies can't authenticate them, so not CSRF-able.
auth/device, auth/device/token Pre-auth device-grant endpoints; never read the session cookie.

Tests

New csrf-guard.test.ts57 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

  • ✅ web lint 0/0, npm run build green, 1223 tests pass
  • ✅ Adversarial 3-lens review (completeness / ordering / scope): 0 real problems — no missing cookie-auth mutation route, guard is the first statement everywhere (confirmed before login's rate-limiter and before the webhook test-dispatch), runs/search exclusion and login inclusion both validated.

Independent of #8 (different files; no overlap).

🤖 Generated with Claude Code

…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]>
@iaj6 iaj6 merged commit e693dae into main Jun 5, 2026
3 checks passed
@iaj6 iaj6 deleted the feat/csrf-same-origin-rollout branch June 5, 2026 12:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant