Skip to content

feat: add Origin/Referer CSRF protection to state-mutating endpoints - #398

Merged
birme merged 1 commit into
mainfrom
security/224-csrf-origin-verification
Oct 6, 2026
Merged

birme merged 1 commit into
mainfrom
security/224-csrf-origin-verification

Conversation

@birme

@birme birme commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add a Fastify onRequest hook (src/csrf.ts) that verifies the Origin header (falling back to the Referer origin) of state-mutating requests (POST/PUT/PATCH/DELETE) against a configurable allowlist.
  • Allowlist is configured via the new CSRF_TRUSTED_ORIGINS env var (comma-separated), resolved in src/config/csrf-origin.ts.
  • Non-breaking by design: it is a complete no-op when CSRF_TRUSTED_ORIGINS is unset/empty, and requests that carry no Origin/Referer header (native/server-to-server clients, tests) are always allowed. A 403 is returned only when a request presents an origin that is not in the allowlist.
  • WHIP/WHEP media routes are exempt, mirroring their existing permissive CORS policy, so cross-origin media clients are not affected.
  • Deliberately avoids cookies/sessions and @fastify/csrf-protection so it does not conflict with the separately-decided auth model (feat: add API key authentication to management endpoints #397). Documented the new env var in readme.md and .env.example.

Test plan

  • Tests pass (npm test)
  • TypeScript compiles (npm run typecheck)
  • Lint clean (npm run lint)
  • Manual: set CSRF_TRUSTED_ORIGINS and confirm a cross-origin POST from a disallowed browser origin is rejected with 403 while same-origin and non-browser clients succeed.

Closes #224

🤖 Generated with Claude Code

Add a non-breaking, defence-in-depth CSRF check for state-mutating
requests (POST/PUT/PATCH/DELETE). An onRequest hook compares the request
Origin header (falling back to the Referer origin) against a configurable
allowlist (CSRF_TRUSTED_ORIGINS) and rejects with 403 only when the
header is present and its origin is not trusted.

Requests with no Origin/Referer header (native/server-to-server clients)
are always allowed, and when CSRF_TRUSTED_ORIGINS is unset the check is a
complete no-op, so existing deployments and clients are unaffected.
WHIP/WHEP media routes are exempt, mirroring their permissive CORS policy.

Deliberately avoids cookies/sessions and @fastify/csrf-protection so it
does not conflict with the separately-decided auth model.

Closes #224

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@birme

birme commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

code-reviewer verdict: LGTM (automated self-review by a separate code-reviewer invocation; recorded as a marker because GitHub blocks state-bearing self-review when author and reviewer are the same account).

A separate code-reviewer invocation independently re-ran the suite in a fresh clone of this PR: typecheck clean, lint 0 errors (317 pre-existing no-explicit-any warnings, unchanged), 439 tests pass across 22 suites. Verified the hook is non-breaking (allows requests with no Origin/Referer, no-op when CSRF_TRUSTED_ORIGINS is unset, never blocks GET/HEAD/OPTIONS, only 403s a present-but-disallowed Origin on POST/PUT/PATCH/DELETE), does not touch auth/cookies (no conflict with #397), and resists the usual bypasses (substring/suffix, Origin: null, Referer spoofing). Two non-blocking over-blocking nits (case-sensitive compare, default-port normalization) both fail closed on an opt-in feature.

@birme
birme merged commit a5aa0ac into main Oct 6, 2026
4 checks passed
@birme
birme deleted the security/224-csrf-origin-verification branch October 6, 2026 06:53
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.

Security: Add CSRF protection to state-mutating API endpoints

2 participants