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

@@ -35,7 +35,10 @@ only and can never drive a session.
with a parse error and `.errorCode` is simply absent (see
[symptom 6](#6-jq-parse-error-instead-of-an-errorcode)). Ten failed attempts from one
IP then get a plain-text `429 Too Many Requests` with `Retry-After`, decaying over 15
minutes (`AUTH_FAILURE_MAX` = 10, `AUTH_FAILURE_WINDOW_MS` = 15 min). **Never retry a
minutes (`AUTH_FAILURE_MAX` = 10, `AUTH_FAILURE_WINDOW_MS` = 15 min). In multi-user
mode the per-IP cap is 50 (`AUTH_IP_FAILURE_MAX`) and is checked before the password,
so past it even a correct password gets the 429; each username is also capped at 10
failed guesses. **Never retry a
failing credential in a loop**: you will lock the address out of the login path for
everything, including the user's browser through a tunnel (tunneled traffic arrives as
127.0.0.1, so one bucket covers it all).
@@ -801,7 +804,7 @@ response echoes.
| sessions, per user | half the global cap (25 by default) in multi-user mode | the same 409, with a different message |
| SSE clients, process-wide | 100 (`MAX_SSE_CLIENTS`) | plain-text `503 Too many SSE connections`; shared with every browser tab |
| active bash tools tracked | 20 per session | oldest entries drop off `active-tools` |
| auth failures per IP | 10, decaying over 15 min | plain-text 429 with `Retry-After`; locks out the login path, so never loop a bad credential |
| auth failures per IP | 10, decaying over 15 min (multi-user: 50 per IP, plus 10 per username) | plain-text 429 with `Retry-After`; locks out the login path, so never loop a bad credential |
Case creation is **uncapped**, which is the one place restraint has to come from you:
every `quick-start` with a new `caseName` creates a real directory on the user's disk.