fix(auth): keep the single-user login throttle at 10, widen only the multi-user pre-gate

Single-user mode has no per-username bucket, so its per-IP bucket is the
only limit on guesses against the one password, and behind a tunnel every
client shares 127.0.0.1. Put it back on AUTH_FAILURE_MAX (10).

AUTH_IP_FAILURE_MAX (50) now gates only the multi-user per-IP pre-gate,
which runs before the scrypt verify and so refuses even a correct password;
the per-username bucket still caps each account at 10.

Move the headroom test out of the single-user suite and add a multi-user
test that sprays wrong passwords across usernames (9 each) and expects 401
for the first 50, then 429 for the 51st and for a correct password.

Update the docs that still described 10 per IP for every mode, and resync
the plugin mirror of the codeman skill.
This commit is contained in:
Aamer Akhter committed 2026-10-22 21:21:49 -04:00
1 parent ebb9e00de9
commit 175e3b8320
12 files changed
+61 -35

No files matched your search

+7 -5
View File
@@ -23,15 +23,17 @@ export const MAX_AUTH_SESSIONS = 100;
/**
* Max failed auth attempts per bucket before 429 rejection. Applies to the
* per-username lockout and the hook-secret bucket.
* single-user per-IP bucket, the multi-user per-username lockout and the
* hook-secret bucket.
*/
export const AUTH_FAILURE_MAX = 10;
/**
* Max failed login attempts per client IP before 429 rejection. Wider than
* AUTH_FAILURE_MAX because one egress IP (NAT, a tunnel, an automated client) can
* carry many users' or retries' failures; the per-username lockout above still
* caps guesses against any one account.
* Max failed login attempts per client IP for the multi-user pre-gate, which
* runs before the scrypt verify and so refuses even a correct password once
* reached. Wider than AUTH_FAILURE_MAX because one egress IP (NAT, a tunnel) can
* carry many users' failures; the per-username bucket still caps guesses against
* each account at AUTH_FAILURE_MAX. Single-user mode does not use this.
*/
export const AUTH_IP_FAILURE_MAX = 50;