CSRF protection uses a hardcoded fallback secret: tokens are forgeable when CSRF_SECRET is unset
Labels / Complexity: bug · security · High Complexity — High
Problem
src/lib/csrf.ts line 4 derives the CSRF secret from the environment with a known fallback:
const CSRF_SECRET = process.env.CSRF_SECRET || 'default-fallback-csrf-secret-key-32-chars-long!';
The fallback string is committed to the repository, so in any deployment where CSRF_SECRET is unset, every CSRF token is signed with a publicly-known key. An attacker can forge valid CSRF tokens for any state-changing request the app protects with this module — in a deployment without the env var, the CSRF defense is equivalent to not having it. This is the same defect class previously fixed elsewhere in the stack: the BackEnd rejected unset secrets for JWT_SECRET (closed #663/#892) and the frontend's middleware.ts still has its own AUTH_SECRET fallback (separate issue) — but csrf.ts is a distinct, still-live instance with its own secret.
Root cause
src/lib/csrf.ts line 4: the || 'default-fallback-csrf-secret-key-32-chars-long!' fallback.
Why this is architecturally hard
- The failure mode must be fail-closed. The fix cannot silently disable CSRF when the secret is missing; the module must refuse to mint/verify tokens (or the app must fail at build/startup via
scripts/validate-env.js, which currently does not require CSRF_SECRET). The contributor must pick the enforcement point and keep the token-verification flow intact.
- Deployments must be updated in the same change. Because the current code has worked (insecurely) without the env var, making it required will break environments that never set it; the issue should note updating the env example/docs so operators set
CSRF_SECRET before the enforcement lands.
Acceptance criteria
- With
CSRF_SECRET unset, token minting/verification fails closed (no tokens issued, or a clear error), and the literal fallback string is gone from src/lib/csrf.ts.
- With
CSRF_SECRET set, current behavior is unchanged (tokens mint and verify).
- A test proves a token signed with the old fallback string is rejected when the real secret is configured.
scripts/validate-env.js (or the equivalent env documentation) requires CSRF_SECRET.
npm run typecheck, npm test, and npm run lint pass.
Out of scope
Replacing the CSRF token scheme (e.g. same-site cookies vs. double-submit) is out of scope.
Getting started
src/lib/csrf.ts — the fallback at line 4 and the mint/verify functions
scripts/validate-env.js — where the env var should be required
src/lib/__tests__/ — check for existing csrf tests to extend
Commands: npm run typecheck, npm test, npm run lint.
Good first files to read: src/lib/csrf.ts, scripts/validate-env.js.
CSRF protection uses a hardcoded fallback secret: tokens are forgeable when CSRF_SECRET is unset
Labels / Complexity: bug · security · High Complexity — High
Problem
src/lib/csrf.tsline 4 derives the CSRF secret from the environment with a known fallback:The fallback string is committed to the repository, so in any deployment where
CSRF_SECRETis unset, every CSRF token is signed with a publicly-known key. An attacker can forge valid CSRF tokens for any state-changing request the app protects with this module — in a deployment without the env var, the CSRF defense is equivalent to not having it. This is the same defect class previously fixed elsewhere in the stack: the BackEnd rejected unset secrets forJWT_SECRET(closed #663/#892) and the frontend'smiddleware.tsstill has its ownAUTH_SECRETfallback (separate issue) — butcsrf.tsis a distinct, still-live instance with its own secret.Root cause
src/lib/csrf.tsline 4: the|| 'default-fallback-csrf-secret-key-32-chars-long!'fallback.Why this is architecturally hard
scripts/validate-env.js, which currently does not requireCSRF_SECRET). The contributor must pick the enforcement point and keep the token-verification flow intact.CSRF_SECRETbefore the enforcement lands.Acceptance criteria
CSRF_SECRETunset, token minting/verification fails closed (no tokens issued, or a clear error), and the literal fallback string is gone fromsrc/lib/csrf.ts.CSRF_SECRETset, current behavior is unchanged (tokens mint and verify).scripts/validate-env.js(or the equivalent env documentation) requiresCSRF_SECRET.npm run typecheck,npm test, andnpm run lintpass.Out of scope
Replacing the CSRF token scheme (e.g. same-site cookies vs. double-submit) is out of scope.
Getting started
src/lib/csrf.ts— the fallback at line 4 and the mint/verify functionsscripts/validate-env.js— where the env var should be requiredsrc/lib/__tests__/— check for existing csrf tests to extendCommands:
npm run typecheck,npm test,npm run lint.Good first files to read:
src/lib/csrf.ts,scripts/validate-env.js.