Skip to content

Add IP-based rate limiting on failed login attempts#598

Open
flightlesstux wants to merge 1 commit intopassbolt:masterfrom
flightlesstux:fix/login-brute-force-rate-limit
Open

Add IP-based rate limiting on failed login attempts#598
flightlesstux wants to merge 1 commit intopassbolt:masterfrom
flightlesstux:fix/login-brute-force-rate-limit

Conversation

@flightlesstux
Copy link
Copy Markdown

Problem

The /auth/login endpoint had no throttling mechanism. An attacker could issue unlimited POST requests against the GPG authentication endpoint with no server-side consequence, enabling:

  • Brute-force enumeration of valid user fingerprints (FAILURE_IDENTITY_NOT_FOUND reveals existence)
  • Repeated credential replay attempts against known accounts
  • Resource exhaustion on the GPG keyring operations

Implementation

Uses CakePHP's built-in Cache to track failed attempts per client IP with a sliding window:

private const LOGIN_MAX_ATTEMPTS = 10;
private const LOGIN_ATTEMPT_WINDOW = 300; // 5 minutes

Flow:

  1. On each POST /auth/login, read the attempt counter for the client IP from cache
  2. If >= 10, return HTTP 429 immediately before any auth processing
  3. On FAILURE_IDENTITY_NOT_FOUND or FAILURE_CREDENTIALS_INVALID, increment the counter (TTL = 300s)
  4. On successful authentication, delete the counter for that IP

Cache key: auth_fail_ + sha1(clientIp) — normalises IPv6 addresses and prevents cache key injection via crafted IP strings.

What does NOT increment the counter

FAILURE_CREDENTIALS_MISSING (HTTP 200) is the first stage of the GPG two-step handshake and is a normal protocol step, not a failed authentication. It is intentionally excluded from the counter.

Impact

  • Limits each IP to 10 failed attempts per 5-minute window
  • No impact on normal usage patterns
  • Successful logins are not throttled and clear the failure counter

The /auth/login endpoint had no throttling mechanism. An attacker
could issue unlimited authentication requests against any account
with no server-side consequence, enabling brute-force enumeration
of fingerprints and replay attacks.

Implementation:
- Track failed attempts per client IP in CakePHP's default cache
- Cache key: sha1(clientIp) prefixed with 'auth_fail_' to prevent
  key injection and normalise IPv6 addresses
- Window: 300 seconds (5 minutes) sliding per-IP counter
- Threshold: 10 failures before a 429 is returned
- Successful authentication clears the counter for that IP

Only FAILURE_IDENTITY_NOT_FOUND and FAILURE_CREDENTIALS_INVALID
increment the counter. FAILURE_CREDENTIALS_MISSING (the first GPG
handshake stage) does not, as it is a normal part of the two-step
protocol and not an authentication failure.
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 12, 2026

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants