m8.3: Authentication and credential hardening
Closes the highest-value authentication and credential gaps from the security audit (ADR-0017), with no new heavy dependencies. Fully backward compatible.
What's new
- Login lockout. After 5 consecutive failed logins (configurable) a username is locked for a cooldown (default 15 minutes), returning 429. The check runs before password verification, is recorded and audited, and resets on a successful login. It is clock-injected and deterministic. Tune with
EPIPHANY_LOGIN_MAX_FAILURESandEPIPHANY_LOGIN_LOCKOUT_SECS(0disables). - Forced password change is enforced. A user with a pending change (a first-run admin, or after an admin reset) is blocked from every data route (403) until they change their password; only change-password, logout, and identity are reachable. The gate lifts immediately on change, with no re-login.
- Owner-only secret files.
security.model(password hashes), the audit log, the run ledger, and the first-run admin-password file are created0600from the start on Unix, so they are never briefly world-readable. On other platforms the data directory's inherited ACL governs (protect your data directory). - No secret on stdout. The generated first-run admin password is written to an owner-only file and only its path is logged; read it once and delete it.
Quality
- New
LoginGuardunit tests, a config-knob test, and an end-to-end acceptance suite (lockout trigger and cooldown release, success-resets-counter, must-change gate then lift). - Hardened after an adversarial review: every secret file is created with restrictive permissions from the start (closing the write-then-chmod window), and the WebSocket route was confirmed to go through the same auth gate. Two flagged items were verified as a false positive (status-code enumeration: a non-existent username also locks, so 429 does not reveal account existence) and benign (a success-clears-lock race only triggers on a correct password).
Compatibility: with the defaults, existing deployments behave the same except that repeated failed logins now lock out and a first-run admin must change the generated password before using data.