fix(security): consolidate proxy to a single src/proxy.ts (CSRF + CSP) #53

Merged
janpeter merged 1 commit from claude/gracious-bell-5c5b3c into main 2026-06-18 18:11:30 +02:00
Owner

What

Next.js 16 registers the proxy only from the src/proxy.ts convention slot (sibling of src/app) via a named export proxy. At HEAD the repo had two proxy files:

  • root proxy.tsexport default, with CSRF + CSP + security headers + auth redirects
  • src/proxy.ts — named export proxy, iron-session auth only

Why this was a trap

Verified empirically (next dev and the production standalone server) which proxy actually executes at runtime:

Files present Executes? (dev + prod) CSRF/CSP/headers
root proxy.ts only (default or named) never OFF
src/proxy.ts only (named) yes ON
both (HEAD) root's code runs ON (fragile)

At HEAD the root file's secure code ran only because src/proxy.ts existed to register the convention slot. Deleting either file as "dead code" would have silently disabled all CSRF/CSP/security headers app-wide.

Change

  • Move the secure logic into src/proxy.ts as a named export proxy (the canonical Next 16 location for a src/app project).
  • Delete the misleading root proxy.ts and root proxy.test.ts.
  • Relocate tests to src/proxy.test.ts: CSRF 403, CSP present, valid-token pass-through.
  • Point src/lib/csrf.ts docs at src/proxy.ts.

Verification

  • npm run build → green, ƒ Proxy (Middleware) registered.
  • npm test → 249 tests, 229 pass, 20 fail — all pre-existing DB ECONNREFUSED (no local Postgres), identical to baseline. No new failures.
  • End-to-end against the production standalone server:
    • POST /api/ready without x-csrf-token403 {"error":"CSRF validation failed"}
    • POST /api/ready with matching csrf cookie+header → not 403
    • GET / without session → 307 → /login
    • GET /login → CSP + X-Frame-Options: DENY + X-Content-Type-Options: nosniff + Referrer-Policy + set-cookie: csrf_token=…; Secure

🤖 Generated with Claude Code

## What Next.js 16 registers the proxy only from the `src/proxy.ts` convention slot (sibling of `src/app`) via a **named** `export proxy`. At HEAD the repo had **two** proxy files: - root `proxy.ts` — `export default`, with CSRF + CSP + security headers + auth redirects - `src/proxy.ts` — named `export proxy`, iron-session auth only ## Why this was a trap Verified empirically (`next dev` **and** the production `standalone` server) which proxy actually executes at runtime: | Files present | Executes? (dev + prod) | CSRF/CSP/headers | |---|---|---| | root `proxy.ts` only (default *or* named) | ❌ never | OFF | | `src/proxy.ts` only (named) | ✅ yes | ON | | both (HEAD) | ✅ root's code runs | ON (fragile) | At HEAD the root file's secure code ran **only because `src/proxy.ts` existed to register the convention slot**. Deleting *either* file as "dead code" would have silently disabled all CSRF/CSP/security headers app-wide. ## Change - Move the secure logic into `src/proxy.ts` as a named `export proxy` (the canonical Next 16 location for a `src/app` project). - Delete the misleading root `proxy.ts` and root `proxy.test.ts`. - Relocate tests to `src/proxy.test.ts`: CSRF 403, CSP present, valid-token pass-through. - Point `src/lib/csrf.ts` docs at `src/proxy.ts`. ## Verification - `npm run build` → green, `ƒ Proxy (Middleware)` registered. - `npm test` → 249 tests, 229 pass, 20 fail — all pre-existing DB `ECONNREFUSED` (no local Postgres), identical to baseline. No new failures. - End-to-end against the **production standalone** server: - `POST /api/ready` without `x-csrf-token` → `403 {"error":"CSRF validation failed"}` - `POST /api/ready` with matching csrf cookie+header → not 403 - `GET /` without session → `307 → /login` - `GET /login` → CSP + `X-Frame-Options: DENY` + `X-Content-Type-Options: nosniff` + `Referrer-Policy` + `set-cookie: csrf_token=…; Secure` 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(security): consolidate proxy to a single src/proxy.ts with CSRF+CSP
All checks were successful
CI / docker-build (pull_request) Successful in 34s
5a029560fe
Next.js 16 registers the proxy only from the src/proxy.ts convention slot
(sibling of src/app) via a named `export proxy`. The repo had two proxy
files: a root proxy.ts (default export, with CSRF + CSP + security headers +
auth redirects) and src/proxy.ts (named export, iron-session auth only).

Verified empirically (next dev + production standalone) that at runtime the
root file's secure code executed only because src/proxy.ts existed to
register the convention slot. A root proxy.ts never runs on its own, and
src/proxy.ts on its own runs whatever logic it contains. So deleting either
file as "dead code" would silently disable all CSRF/CSP/security headers.

Consolidate to one canonical proxy: move the secure logic into src/proxy.ts
as a named `export proxy`, delete the root proxy.ts and root proxy.test.ts,
relocate the tests to src/proxy.test.ts (now covering CSRF 403, CSP present,
and a valid-token pass-through), and point the csrf.ts docs at src/proxy.ts.

Verified: `npm run build` green; `npm test` unchanged vs baseline (only the
pre-existing DB ECONNREFUSED failures); and end-to-end CSRF 403, CSP +
security headers, and the /login auth redirect confirmed against both
`next dev` and the production standalone server.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
janpeter/Media-Organizer!53
No description provided.