Files
felhom.eu/REPORT-login-gate-2026-09-29.md
T
2026-09-29 09:22:56 +02:00

6.9 KiB
Raw Blame History

REPORT — 2026-09-29: demo-hp's two open default logins closed; the setup gate SPIKED, PASSED and BUILT; every hard-coded default replaced

Architecture read first: 01-topology-and-trust.md §5 (trust boundaries — it had no statement of who may reach an app; now it does), 04-control-plane-authorization.md (control plane only — nothing about app reachability), 09 §3 decisions 45–46, app-catalog-felhom.eu/FIRST-ADMIN.md. Controller v0.280.0 (one release). Floor 0.280.0; both demo boxes run it. Evidence: documentation/audits/login-gate-2026-09-29/ (A, B, C, D).

The Parts

Part Step State Note
— rulings recorded first done decision 46 + the demo-login ruling in 09 §3 before any work
A1 read-only: defaults still work done bookstack and calibre-web: default signed in (302 → /), wrong refused (A1)
A2 change through the app's own route done bookstack artisan bookstack:create-admin --initial; calibre-web cps.py -s with a special-character password. Default refused, new signs in, wrong refused, through each app's own login form
A3 save in ~/.config/credentials done four lines appended in the file's format (DEMO_HP_BOOKSTACK_USER/_PW, DEMO_HP_CALIBRE_USER/_PW), file 0600, read back equal; values in no evidence, commit or log (value scan before every evidence commit)
A4 pages stop warning done, changed known_login.go could NOT learn of a manual change, and bookstack's page had ALREADY stopped warning while its default worked (R-710). Built "I changed it" in v0.280.0 (an honest household/operator record, not a faked after_install result) and pressed it on demo-hp: both sentences gone (A3)
B1 dashboard session on the app's address done the cookie is host-only — it cannot be seen there; a redirect handshake instead, nothing widened
B2 stranger / household in a browser done stranger: gate page or 401, never the app; household: 0.2 s, no extra step; one sign-in on a phone not signed in
B3 "setup done" probes done n8n and immich measured flipping; 14 of 34 have a probe (then 2 measured, 12 upstream), 20 need the button
B4 after the gate opens done immich's phone-app API (Bearer) and n8n's API unchanged; the gate stopped and removed
B5 cost done ~2 ms; controller down → gated apps 500 (closed); phone app first → 401 until the web setup
B6 exit test PASSED written before any build: B/B-VERDICT.md
C1 controller v0.280.0: the gate done written before the first start (a failed write refuses the install); probe loop 20 s; button; restart keeps it; restore keeps the record; kept data never gates. hu + en copy, informal, no "please"; parity green
C2 tests + red-proofs done TestSetupGate_* (stacks 9, web 4 + page 2), all green; RP1–RP12 each seen failing on an assertion
C3 live on 3–5 class-4 apps done (4) immich (phone app), n8n, audiobookshelf (probe), uptime-kuma (button): ~530 stranger polls during the installs, 0 app answers before each gate opened; the probes opened 3 gates ≤ 27 s after setup; the press opened the 4th; a controller restart kept the 4th closed and the household's pass valid
C3 restore does not re-gate not live proven by test only (TestSetupGate_ARestoreKeepsTheRecord): a per-app backup on 9202 needs a whole-box backup, which drills must not run (R-648)
C4 design record + decision 46 outcome done 01 §5 "who may reach an app, and through what"; 09 §3 decision 46 outcome
C4 floor done 0.280.0 after every live proof passed; both demo boxes delivered
D1 mealie, wger done after_install, password as sys.argv[1]; fresh-install proof: default refused, generated signs in, wrong refused. Found and fixed R-712 (wger refused every browser sign-in: CSRF)
D2 calibre-web done generate: password:24:special (server + install page; 2000 JS runs in node, 0 bad); cps.py -s as abc; fresh-install proof as D1
D3 romm, zipline notes done removed (hu + en); first steps say create the admin; romm's page on demo-hp no longer warns
D4 R-708, R-709 done grafana ${…:?…} (compose refuses empty/unset); password fields off the page with a reveal eye (live: the value is not in the HTML)
D5 tests + red-proofs + live done RP13–RP16; live on 9202

Claims in the brief that turned out wrong (or right), named

  • "No class-4 app is reachable except through traefik" — right (read: only crafty-controller publishes ports, and it is class 1). Nuance: wanderer publishes a SECOND host (its database admin) — a gate must cover every host an app's labels publish; the built gate does.
  • "Most class-4 apps expose a setup-done status" — wrong: 14 of 34 (now 3 measured, 11 upstream); 20 need the household's button.
  • "The dashboard session can be checked on an app's subdomain without widening it" — wrong as stated: the cookie is host-only and never reaches an app host. A redirect handshake (a 60-second, one-use, host-bound token minted on the dashboard's own host) does the check instead — and nothing is widened.
  • "immich's phone app works unchanged after the gate opens" — right, measured through its API (login → Bearer → /users/me, /server/ping, /server/version, all 200); the real phone app was not run.
  • "known_login.go can learn of a manual password change" — wrong: it knew only after_install records. And worse than the brief assumed: bookstack's page on demo-hp had already stopped warning while its default still worked (an absent record read as "not run yet" for ever — R-710). Fixed; proven live.

Also found

  • The household's "Done" press trusts the household. The uptime-kuma proof pressed it without doing the setup, and the app then answered anyone. The page tells the household to press after the setup; nothing checks it (no probe exists for uptime-kuma over HTTP). Recorded in decision 46's outcome.
  • A security review of the drill commit flagged mealie's and wger's commands (the password pasted into Python code). Fixed before the live catalog (argv). claper's Elixir command has the same shape → R-713.

Rows

Opened: R-710 (closed the same day), R-711, R-712 (closed), R-713. Closed: R-708, R-709, R-710, R-712. Narrowed: R-707 (30 of 37 left). Register 346 → 350 rows.

Teardown

Machines: 9202 — the spike's container, file and two apps removed; the seven Part C/D apps removed through the product (immich, audiobookshelf and calibre-web kept their scratch-drive folders — R-442's refusal, as in earlier sessions); no gate file left; the spike's python and node images removed; back on the live catalog; the drill catalog reset to live main. demo-hp 9201 — the two admin passwords changed and the two "I changed it" records (the operator's ruling); nothing else. demo-felhom — nothing. Host: nothing. Hub: floor 0.280.0. ep0: untouched.