Security [CRITICAL]: Rate-limiter only keys by email — IP-based brute-force and targeted lockout possible #37
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Login rate-limit (
authRateLimiter(email)) keys only on email. An attacker can (a) enumerate/brute-force arbitrary emails from one IP with no global limit, and (b) lock out any known user by submitting 5 wrong passwords in 15 min (account-lockout DoS). Additional:resetPasswordrate-limit keys on the reset-token (always new) → effectively no limit.Evidence
apps/web/src/server/auth.ts:55 — await authRateLimiter(email.toLowerCase())packages/api/src/router/auth.ts:81 — rl = await authRateLimiter(input.token)packages/api/src/middleware/rate-limit.ts:223 — empty key returns allowed:truepackages/api/src/middleware/rate-limit.ts:218 — Redis-flap degrades to maxRequests/10, self-locks legit userspackages/api/src/router/user.ts:245 — TOTP-verify rate-limit keyed on userId only (lockout DoS)Impact
(a) Credential-stuffing attacks across emails unbounded. (b) Targeted account-lockout DoS against any known user. (c) Reset-password endpoint lacks effective limit → DB load DoS. (d) TOTP endpoint allows per-user lockout.
Proposed Fix
Add second rate-limit bucket keyed on request IP (
x-forwarded-forviatrustProxyconfig). Key pattern:authRateLimiter(['email:' + email, 'ip:' + ip]). Fail closed on empty key. For reset-password: key on resolved email AFTER token lookup, OR on IP with higher limit. For TOTP: add IP-second-key.Acceptance Criteria
allowed: false(not true)docs/security-architecture.mdwith key-schema tableParent Epic: #1
Source: Full-Codebase Security Audit 2026-04-16 (A-3, A-4, A-9, A-18)
Resolved in main commit
3c5d1d3(security: rate-limit IP-keyed, fail-closed on empty key). Rate-limit now keys on IP + email; empty-key paths fail closed.