The opt-in multi-user feature's only enforcement is web-layer scoping
(all sessions share one OS account). An adversarial review found 8 critical
+ 7 high cross-user holes that defeated it, plus mediums; all fixed here.
Single-user (flag-off) behavior stays byte-identical apart from documented
consistency deltas.
Ownership / confinement:
- DELETE /api/sessions (bulk) + /:id now owner-scope / findSessionOrFail
- quick-start, cron (create+fire), scheduled runs confine workingDir to the
owner's space; case link/docker-link/docker-import confine the host path
- resolveCasePath no longer resolves linked cases for non-admins; foreign
remote/docker cases are skipped (fall through to the caller's own local case)
- history, subagents/workflows, mux-sessions, orchestrator, cron run-history,
away-digest, and remote/docker host reads are owner- or admin-scoped
Permission policy (section 6.3):
- non-granted users are downgraded at every spawn site incl. legacy
/api/scheduled, PlanOrchestrator one-shots, remote launch, and the cron-fire
gemini/codex bypass switches; resolveClaudeModeForUsername now fails closed
Auth / store:
- verify-first login throttle (a correct password is never locked out),
/ws terminal subject to the change-password lockbox, cookie fast-path
re-validates identity live, role/grant changes revoke sessions, admin delete
runs the last-admin guard before any teardown
- users.json: distinguish missing (ENOENT) from corrupt/unreadable so a bad
read can't overwrite all accounts; unique per-process temp write path
Event streams:
- debounced session:updated + batched task:updated, clipboard, and push
notifications route by owner (fail closed); getLightState hides machine-wide
globalStats from non-admins
Tests: two suites updated to assert the fixed (secure) behavior. tsc, eslint,
and test:ci all green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a parallel multi-user auth branch (the single-user Basic-auth path is
left byte-identical). Off unless CODEMAN_MULTIUSER/--multiuser.
- middleware/auth.ts: mode-selecting registerAuthMiddleware. New async
multi-user hook verifies username:password against the user store (scrypt),
mints identity-carrying cookies, decorates req.authUser, enforces a per-IP
AND per-username failure bucket, and the mustChangePassword lockbox. The
hook-secret loopback bypass is now a single shared helper used by both
branches. FastifyRequest.authUser module augmentation.
- ports/auth-port.ts: AuthSessionRecord gains username/role/mustChangePassword.
- user-store.ts: verifyPassword (timing-equalized against user enumeration).
- route-helpers.ts: getAuthUser (synthetic admin fallback), canAccessOwned,
requireAdmin, revokeUserSessions; findSessionOrFail gains an optional req for
a NOT_FOUND owner check (dormant until phase 3 wires callers).
- routes/me-routes.ts: GET /api/me (synthetic admin in single-user) and
POST /api/me/password (verify current, min 8, clear mustChangePassword,
revoke other sessions).
- QR: QrTokenRecord + AuthSessionRecord carry a username; tunnel-manager
mintUserToken / consumeTokenWithIdentity / getQrSvgForCode; /q/:code binds
the cookie to the token's user (rejects identity-less tokens in multi-user);
GET /api/tunnel/qr mints a per-user token. Single-user keeps the rotating token.
- server.ts: bootstrap the initial admin from CODEMAN_USERNAME/PASSWORD on first
boot (refuse to start with no users); multi-user with >= 1 user satisfies the
non-loopback auth requirement and the tunnel-enable guard; userFailures bucket
disposal.
- types/api.ts: FORBIDDEN, PASSWORD_CHANGE_REQUIRED, USER_EXISTS, USER_NOT_FOUND,
LAST_ADMIN error codes (message + status wired).
- Session.owner field + getter/setter, SessionState.owner, MuxSession.owner,
CreateSessionOptions.owner (foundation for phase 3 ownership threading).
Tests: test/multiuser-auth.test.ts (10, live server on 3170/3171). Existing auth
suite (auth-security, qr-auth, cod54-hook-event, network-auth-policy) unchanged
and green; full test:ci sweep passes (3519 tests).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>