mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 02:29:33 +01:00
fix: apply the review items for #684 at merge
Multi-user: add a (username, IP) failure bucket checked before verifyPassword, capped at AUTH_FAILURE_MAX, with the same decay as the other buckets; the per-IP pre-gate stays at AUTH_IP_FAILURE_MAX and the per-username bucket stays after the verify A correct password now also clears that account's (username, IP) bucket, beside the IP and per-username buckets The new bucket is disposed on server stop like the others; the comments above the gate and the constants' JSDoc describe the three buckets Tests: 10 wrong then the correct password from one IP is a 429, the same account logs in from another IP, another account behind that IP is unaffected, and a correct password clears the pair bucket test/multiuser-auth.test.ts: the spray test loops to AUTH_IP_FAILURE_MAX instead of a literal 50 Reworded the rate-limit lines in auth-config.ts, security-architecture.md, README.md, README.zh-CN.md, wiki/Security.md, endpoints.md (plus the plugin mirror), CLAUDE.md and architecture-invariants.md docs/wiki/Remote-Access.md: add the multi-user note to the tunnel page Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
175e3b8320
commit
b7897b6d0e
13 files changed
+173
-49
No files matched your search
@@ -1163,7 +1163,7 @@ Tests: `test/mobile-prompt-composer.test.ts` (in the CI gate, deliberately not u
|
||||
| **CSRF / Origin** | Always-on cross-site Origin guard rejects state-changing requests from foreign origins (covers self-update, session create/input, settings/tunnel toggles). **A missing Origin is allowed** so curl/CLI and Claude Code hooks keep working. The global body parser keeps `text/plain` RAW (no auto-JSON-parse, which had enabled simple-request CSRF); `/api/crash-diag` self-parses. WebSocket upgrade validates Origin+Host (anti-CSWSH) in `ws-routes.ts`. Added in `c669518` (closes 2026-06-09 review CRITICALs) |
|
||||
| **QR Auth** | Single-use 6-char tokens (60s TTL) for tunnel login. See `docs/qr-auth-plan.md` |
|
||||
| **Sessions** | 24h cookie (`codeman_session`), auto-extend, device context audit |
|
||||
| **Rate limit** | 10 failed logins/IP → 429 (15min decay). Multi-user mode instead has a 50/IP pre-gate (`AUTH_IP_FAILURE_MAX`, checked before scrypt) plus a 10-per-username bucket (429 from the 10th failed guess). QR has separate limiter |
|
||||
| **Rate limit** | 10 failed logins/IP → 429 (15min decay). Multi-user mode instead has two gates checked before scrypt, so past either even a correct password from that IP gets 429: 50/IP (`AUTH_IP_FAILURE_MAX`) and 10 per (username, IP) (`AUTH_FAILURE_MAX`; the same account still logs in from another IP). A 10-per-username bucket (multi-user only) is checked after the verify: 429 to failed guesses from the 10th on, from any IP, never to a correct password. QR has separate limiter |
|
||||
| **Hook bypass** | `/api/hook-event` (and `/api/status-telemetry`, the statusLine exporter) skip Basic auth (localhost-only, schema-validated). When auth is active (`CODEMAN_PASSWORD` set), the loopback bypass requires the per-instance `X-Codeman-Hook-Secret` header **unconditionally** — COD-54 introduced it tunnel-gated; COD-91 (PR #127) made it always-on because Codeman can't detect a user's own loopback reverse proxy (own cloudflared/`tailscale serve`/nginx → 127.0.0.1), closing that residual plain-bypass gap. Hook curls cat the secret file at exec time via `$CODEMAN_HOOK_SECRET_FILE` (session env, `config/hook-secret.ts`); a missing/wrong secret gets 401 and rate-limits in a dedicated bucket (never locks out login). Tunnel enable **refuses** without `CODEMAN_PASSWORD` unless exposure is acknowledged — via `CODEMAN_ALLOW_UNAUTHENTICATED_NETWORK=1` (env, COD-55) **or** the per-request `acknowledgeUnauthTunnel:true` action field (1.1.9): the welcome/settings tunnel toggle pops a security confirm dialog and, on confirm, resends with that flag (server logs a loud warning on every passwordless tunnel start; curl/API stay refused without password/env/flag). The flag is an action field, never persisted |
|
||||
| **Env vars** | `CODEMAN_MUX` (managed session), `CODEMAN_API_URL` (auto-set for hooks), `CODEMAN_ALLOWED_HOSTS` (extra Host/Origin allowlist entries for reverse proxies, comma-separated; bare `.suffix` matches subdomains), `CODEMAN_DOCKER_BRIDGE_HOOKS`=1 (opt-in hooks-only listener on the docker bridge gateway so in-container hooks reach a loopback-bound server; bind IP from `CODEMAN_DOCKER_BRIDGE_HOST` or auto-detect) |
|
||||
| **Validation** | Zod schemas, Unicode-aware path allowlist regex, env prefix allowlist (`CLAUDE_CODE_*`/`OPENCODE_*`/`CODEX_*`/`GEMINI_*`/`GOOGLE_*`/`ANTIGRAVITY_*`/`PI_*`) |
|
||||
|
||||
@@ -149,7 +149,7 @@ by presence in a server‑side map, so they cannot be forged offline.
|
||||
|
||||
### Rate limiting / lockout recovery
|
||||
|
||||
Failed logins are tracked **per IP**: 10 failures (`AUTH_FAILURE_MAX`) → `429`, with a 15‑minute decay. Multi-user mode replaces that with a wider per-IP **pre-gate** of 50 failures (`AUTH_IP_FAILURE_MAX`), checked before the scrypt verify, plus a **per-username** bucket that exists in multi-user mode only and answers `429` from the 10th failed guess (`AUTH_FAILURE_MAX`; the count is incremented before the check), so the wider per-IP cap (for users sharing one egress IP) does not widen guessing against any one account.
|
||||
Failed logins are tracked **per IP**: 10 failures (`AUTH_FAILURE_MAX`) → `429`, with a 15‑minute decay. Multi-user mode replaces that with three buckets, all with the same decay: a wider per-IP **pre-gate** of 50 failures (`AUTH_IP_FAILURE_MAX`), checked before the scrypt verify, which bounds scrypt cost from one source and tolerates many users behind one egress IP; a **per-(username, IP)** gate of 10 failures (`AUTH_FAILURE_MAX`), also checked before the verify, which is what caps verified guesses against one account from one source; and a **per-username** bucket, checked after the verify, that answers `429` to failed guesses from the 10th on, from any IP (the count is incremented before the check). The last two exist in multi-user mode only. So the wider per-IP cap does not widen guessing against any one account from one IP, and other IPs cannot lock an account out.
|
||||
The QR path has its own separate limiter.
|
||||
|
||||
The lockout check sits **after** the cookie/credential checks (step 4, not first).
|
||||
@@ -158,9 +158,13 @@ immediately** even while an attacker is hammering the same IP — important beca
|
||||
all traffic through a tunnel shares one source IP (loopback). Wrong credentials
|
||||
are still counted and still hit the `429` at the threshold, so brute‑force
|
||||
protection is unchanged.
|
||||
The multi-user per-IP pre-gate is the exception: it runs before the password is
|
||||
verified, so once it trips even a correct password gets `429` until the decay,
|
||||
which is why its cap is wider than the per-username one.
|
||||
The two multi-user pre-verify gates are the exception: once the per-IP pre-gate
|
||||
or an account's (username, IP) gate trips, even a correct password from that IP
|
||||
gets `429` until the decay (for the pair gate, only for that account; the same
|
||||
account from another IP still logs in), which is why the per-IP cap is the
|
||||
widest. The per-username bucket stays after the verify on purpose: checked
|
||||
first, a username-wide lockout let throwaway IPs keep a known account (admin
|
||||
included) locked out even with the right password.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -239,6 +239,12 @@ minute decay.
|
||||
A valid cookie or a correct password recovers immediately even while an attacker is hammering
|
||||
the same IP, which matters because all tunnel traffic arrives from one loopback address.
|
||||
|
||||
Multi-user mode is different: there the per-IP cap is 50, each account allows ten failed
|
||||
guesses from one IP, and both are checked before the password. Behind a tunnel every client
|
||||
shares the loopback address, so past 50 failures in total a correct password waits for the
|
||||
decay for everyone, and past ten failures for one account it waits for that account (the
|
||||
account still logs in from another IP). A valid cookie keeps working throughout.
|
||||
|
||||
## Terminal alternatives
|
||||
|
||||
You do not have to use a browser. `codeman tui` is a full-screen session dashboard that
|
||||
|
||||
@@ -53,7 +53,7 @@ When authentication is enabled:
|
||||
| ------------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| **HTTP Basic** | `CODEMAN_USERNAME` (default `admin`) and `CODEMAN_PASSWORD`. |
|
||||
| **Session cookie** | A 256-bit opaque token validated server side, so it cannot be forged offline. 24 hours, extended on activity, with a device-context audit trail. |
|
||||
| **Rate limiting** | Ten failed attempts per IP produce a `429` with a 15 minute decay. A correct password or valid cookie recovers immediately even under attack, which matters because all tunnel traffic shares one loopback address. In multi-user mode the per-IP cap is 50 and is checked before the password, so past it a correct password also waits for the decay; each username is also capped at ten failed guesses. |
|
||||
| **Rate limiting** | Ten failed attempts per IP produce a `429` with a 15 minute decay. A correct password or valid cookie recovers immediately even under attack, which matters because all tunnel traffic shares one loopback address. In multi-user mode the per-IP cap is 50, and each account allows ten failed guesses from any one IP. Both are checked before the password, so past either a correct password from that IP also waits for the decay (the account still logs in from another IP, and a valid cookie keeps working). From the tenth failed guess on, an account's wrong guesses get a `429` from every IP, which never refuses a correct password. |
|
||||
| **QR auth** | Single-use 60-second tokens with their own separate rate limiter, so a mistyped password cannot lock out QR login. |
|
||||
| **Hook endpoints** | The hook and telemetry endpoints skip Basic auth because they are called from localhost by the CLI, but when auth is on, that bypass additionally requires a per-instance hook secret. |
|
||||
|
||||
|
||||
Reference in new issue
Block a user