You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The admin login endpoint POST /api/token enforces a global per-IP bucket: 4 failed attempts triggers a 599-second lockout. Because the bucket is per-IP and all users behind a corporate NAT, VPN, or CDN share the same egress IP, any user who fat-fingers their password 4 times locks out every other user at that IP. The 599-second lockout duration also doubles as a Denial-of-Service primitive — an attacker with any rotating proxy can keep a target organisation locked out indefinitely.
Credential attack — per-IP buckets are bypassable trivially via rotating proxy / Tor / ipv6 /64, so the protection is cosmetic against motivated attackers.
DoS — once tripped, the 599s global lockout blocks all users sharing that source IP; an attacker can cycle proxies to hold the lockout continuously.
Note: UnderDefense issue #176 (Email OTP rate limiting) was fixed separately. The /api/token endpoint has a different and broken rate-limit design that was not addressed by that fix.
Acceptance Criteria
Rate limit split into two independent buckets:
Per-account (sliding window, e.g. 5 fails / 15 min / account) — never affects other accounts
Per-IP (e.g. 30 fails / 5 min / IP) — high enough to not impact legitimate shared-IP users
Replace global lockout with progressive delay (e.g. 1s → 2s → 4s → … per-IP) rather than a hard block
CAPTCHA-after-3-fails preferred over hard lockout (lockout makes endpoint unusable to legitimate users; CAPTCHA does not)
Security log event emitted on lockout state-change so operators see when the global bucket is being exercised
Test: assert that 4 rapid bad-password attempts from IP-A do not affect a valid login attempt from IP-B
Technical Notes
Rate limit logic lives in routers/auth.py — look for check_login_rate_limit
The 599-second lockout duration appears to be a redis TTL — consider replacing with a per-IP exponential backoff TTL that resets on success
Email OTP endpoint (/api/auth/email/verify) was separately fixed in SEC: Email OTP Verification Lacks Rate Limiting #176; validate that fix used per-account counters and did not introduce the same global-IP pattern
Summary
The admin login endpoint
POST /api/tokenenforces a global per-IP bucket: 4 failed attempts triggers a 599-second lockout. Because the bucket is per-IP and all users behind a corporate NAT, VPN, or CDN share the same egress IP, any user who fat-fingers their password 4 times locks out every other user at that IP. The 599-second lockout duration also doubles as a Denial-of-Service primitive — an attacker with any rotating proxy can keep a target organisation locked out indefinitely.Context
AISEC (scan 3aad5469, 2026-04-28), CVSS 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H), CWE-307.
Two concrete problems:
Note: UnderDefense issue #176 (Email OTP rate limiting) was fixed separately. The
/api/tokenendpoint has a different and broken rate-limit design that was not addressed by that fix.Acceptance Criteria
Technical Notes
routers/auth.py— look forcheck_login_rate_limit/api/auth/email/verify) was separately fixed in SEC: Email OTP Verification Lacks Rate Limiting #176; validate that fix used per-account counters and did not introduce the same global-IP pattern