Skip to content

m8.3: Authentication and credential hardening

Choose a tag to compare

@WilliamSmithEdwardSandbox WilliamSmithEdwardSandbox released this 15 Jun 04:15

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_FAILURES and EPIPHANY_LOGIN_LOCKOUT_SECS (0 disables).
  • 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 created 0600 from 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 LoginGuard unit 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.