E2E suite drives registrations from one IP at parallel volume;
production limits trip and flake tests. Same opt-out pattern as the
fail-loud secret/DB-URL guards.
Adds per-process in-memory fixed-window rate limiting (lib/rate-limit.server.ts)
and applies it across /api/auth/login and /api/auth/register:
- All login steps: 30 attempts per IP per minute (defense against
step-fanout flooding).
- finish-passkey: 10 per IP per minute (assertions don't usefully retry
faster).
- magic-link generation: 3 per email per 5 min + 10 per IP per 5 min
(defeats inbox-spam-the-victim + cross-email IP fanout).
- verify-code: 10 per email per 15 min — makes 6-digit code brute force
(10^6 search) infeasible before code expiry.
- /api/auth/register: 10 per IP per hour (legitimate signup completes
in 2-3 requests; sustained churn from one IP is account spam).
Single-instance is fine for the flagship's current topology (one journal
container). When we horizontally scale we revisit with a Postgres- or
Redis-backed store. Buckets self-clean on next read and a 5-minute
background sweep drops stale entries.
Client IP: honors X-Forwarded-For first entry (Caddy in front of the
journal sets it), falls back to a stable "unknown" bucket so the
limiter still bites when the header is missing.
Tests: rate-limit.server.test.ts (8 cases — exhaustion, independent
scopes/keys, remaining countdown, reset window, XFF parsing, fallback,
trimming). Existing auth tests still pass; limits sized to not trip
during the ~7 test cases that all share the "unknown" IP bucket.
Full repo: pnpm typecheck, pnpm lint, pnpm test all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>