Mechanical .max() bounds across 9 router schemas per the convention in
#51: IDs at 64, names at 200, search/filter strings at 500, arrays at
100-5000 depending on domain. Webhook secret bounded at min(16)/max(256).
Reports route now validates startDate/endDate via zod with year bounds
and rejects end<start. SSE timeline route enforces a per-user connection
cap of 8 (returns 429 with Retry-After). tRPC route rejects bodies over
2 MiB via Content-Length check before auth/DB work.
Covers 12 call-sites listed in #51. ESLint rule and zod conventions doc
remain as follow-up.
Three related fixes:
- Cookie secure flag now tracks AUTH_URL scheme (https → Secure),
not NODE_ENV — staging over HTTPS with NODE_ENV!=production used
to ship Set-Cookie without Secure. Cookie name gains __Host-
prefix when Secure is on.
- jwt() callback no longer swallows session-registry write failures;
concurrent-session cap is now fail-closed.
- Session callback no longer copies token.sid onto session.user.jti.
The tRPC route handler reads the JTI directly from the encrypted
JWT via getToken() so it stays server-side.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Rate-limiter now accepts string | string[] so callers can key on
multiple buckets simultaneously. If any bucket is exhausted the
request is denied, which lets login/TOTP/reset-password throttle on
BOTH user identifier and source IP without either becoming a bypass.
Fail-closed: empty/whitespace-only keys now deny by default instead
of silently allowing unbounded attempts (was CWE-307 gap).
Degraded-fallback divisor reduced from /10 to /2 — the old aggressive
clamp forced-logged-out legitimate users during brief Redis outages;
/2 still meaningfully slows distributed brute-force.
Callers updated:
- auth.ts (login): both email: and ip: buckets
- auth router requestPasswordReset: email + IP
- auth router resetPassword: IP before lookup, email-reset after
- invite router getInvite/acceptInvite: IP
- user-self-service verifyTotp: userId + IP
TRPCContext now carries clientIp; web tRPC route extracts it from
X-Forwarded-For / X-Real-IP.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three related fixes to prevent E2E test runs from disrupting real user sessions:
1. auth.ts: skip active_sessions registration in E2E mode
E2E logins now return early after setting token.sid without writing
to active_sessions. Prevents test sessions from kicking real user
sessions via the concurrent-session limit.
2. trpc/route.ts: skip active_sessions validation in E2E mode
Pairs with (1): if registration is skipped, validation must be too,
otherwise every storageState-based test gets a 401 "Session revoked".
3. docker-compose.yml: hardcode Docker-internal DATABASE_URL + E2E_TEST_MODE
Previously ${DATABASE_URL:-postgres:5432} picked up the host's
localhost:5433 override and passed it into the container, where
localhost refers to the container itself — breaking db:migrate:deploy
on container recreate. Now hardcoded to postgres:5432.
Also adds E2E_TEST_MODE=true to the dev container environment.
Result: 21/21 dev-system E2E tests pass, test runs leave zero footprint
in active_sessions and rate limiter counters for real user accounts.
The timeline disruption caused by test sessions kicking the admin's
real browser session is also resolved.
Co-Authored-By: claude-flow <ruv@ruv.net>
The active_sessions table was never migrated to production — the model
was added to the Prisma schema via db push only. prisma migrate deploy
was a no-op because no migration directories existed.
Without the table, prisma.activeSession.findUnique() throws P2021,
crashing the tRPC handler with 500 on every authenticated request.
This silently emptied all admin pages (users, system-roles, etc.).
Changes:
- Wrap the jti ActiveSession lookup in try-catch so the tRPC handler
degrades gracefully (fail-open) if the table is temporarily missing
- Add packages/db/prisma/migrations/20260401000000_active_sessions/
so prisma migrate deploy creates the table on next production deploy
(idempotent via IF NOT EXISTS — safe if table already exists)
Co-Authored-By: claude-flow <ruv@ruv.net>
#19 MFA QR code: render locally via qrcode package, remove external qrserver.com request
#20 Webhook SSRF: add ssrf-guard.ts with DNS-verified IP blocklist; enforce on create/update/test/dispatch
#21 /api/perf: fail-closed when CRON_SECRET missing; remove query-string token auth
#22 CSP: remove unsafe-eval and unsafe-inline from script-src in production builds
#23 Active session registry: forward jti into session object; validate against ActiveSession on every tRPC request
#24 Docker: add missing packages/application to Dockerfile.dev; fix pnpm-lock.yaml glob;
run db:migrate:deploy on container start so a fresh checkout boots without manual steps
Also: fix pre-existing TS error in e2e/allocations.spec.ts (args.length literal type overlap)
Co-Authored-By: claude-flow <ruv@ruv.net>