Background
Several authentication-adjacent endpoints today throttle by target identity rather than by source IP:
- Worker login (
features/cerberusweb.core/api/uri/login.php _routeAuthenticate) — counts worker.login.failed activity entries, keyed to $worker_id. Credential stuffing across many accounts bypasses this since each victim's counter sees only one attempt.
- Password recovery (
_routeRecover) — keyed on recover:worker:%d cache entry. An attacker spraying recovery requests across many emails isn't slowed.
- MFA TOTP challenge —
mfa.fail_count is per-login-state. Re-entering the flow gets a fresh counter.
Cerb's standing recommendation is that IP-level rate limiting belongs in nginx/WAF, and that's still the right primary layer. But an in-app, per-IP counter via the metrics service would be a useful defense-in-depth on installs where a WAF isn't configured.
Proposal
Use the metrics service to track failed attempts keyed on source IP across:
auth.login.failed (worker password)
auth.recover.requested (worker password recovery code send)
auth.recover.failed (recovery code wrong-guess)
auth.mfa.failed (TOTP wrong-guess)
When a per-IP counter crosses a threshold within a short window (e.g. ≥20 in 15 min), apply progressive slowdown / temporary block.
Dependency
Requires #1554 (Per-metric retention periods) to land first. IP-keyed series have effectively unbounded cardinality (every probing IP gets its own series) and the data is only useful for ~15–60 minutes — keeping it forever would balloon the metrics tables on any internet-exposed Cerb. Need a per-metric retention TTL knob (e.g. expire `auth.login.failed.by_ip` 1d series after 7d) before this is worth shipping.
Context
This came out of an internal login + SSO audit (2026-05-06) as finding F9. F1–F5 from that audit have already shipped to 11.1.9.
Background
Several authentication-adjacent endpoints today throttle by target identity rather than by source IP:
features/cerberusweb.core/api/uri/login.php_routeAuthenticate) — countsworker.login.failedactivity entries, keyed to$worker_id. Credential stuffing across many accounts bypasses this since each victim's counter sees only one attempt._routeRecover) — keyed onrecover:worker:%dcache entry. An attacker spraying recovery requests across many emails isn't slowed.mfa.fail_countis per-login-state. Re-entering the flow gets a fresh counter.Cerb's standing recommendation is that IP-level rate limiting belongs in nginx/WAF, and that's still the right primary layer. But an in-app, per-IP counter via the metrics service would be a useful defense-in-depth on installs where a WAF isn't configured.
Proposal
Use the metrics service to track failed attempts keyed on source IP across:
auth.login.failed(worker password)auth.recover.requested(worker password recovery code send)auth.recover.failed(recovery code wrong-guess)auth.mfa.failed(TOTP wrong-guess)When a per-IP counter crosses a threshold within a short window (e.g. ≥20 in 15 min), apply progressive slowdown / temporary block.
Dependency
Requires #1554 (Per-metric retention periods) to land first. IP-keyed series have effectively unbounded cardinality (every probing IP gets its own series) and the data is only useful for ~15–60 minutes — keeping it forever would balloon the metrics tables on any internet-exposed Cerb. Need a per-metric retention TTL knob (e.g. expire `auth.login.failed.by_ip` 1d series after 7d) before this is worth shipping.
Context
This came out of an internal login + SSO audit (2026-05-06) as finding F9. F1–F5 from that audit have already shipped to 11.1.9.