Session close 2026-09-29: R-710 closed live on demo-hp; STATUS, CONTEXT, REPORT-login-gate-2026-09-29
gates / gates (push) Successful in 26s
gates / gates (push) Successful in 26s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
+11
@@ -16,6 +16,17 @@
|
||||
> and holds nothing of its own; this file does hold its own content, namely the standing rulings below.
|
||||
|
||||
|
||||
> **2026-09-29 — operator rulings:** (1) CC changed the admin passwords of demo-hp's installed bookstack and calibre-web
|
||||
> (stored in the operator's credentials file as `DEMO_HP_BOOKSTACK_*` / `DEMO_HP_CALIBRE_*`, values never in a repo);
|
||||
> (2) decision 46 — the setup gate is SPIKED first. **The spike PASSED** (`audits/login-gate-2026-09-29/B/B-VERDICT.md`);
|
||||
> controller **v0.280.0** built it (`internal/stacks/setup_gate.go`, `internal/web/setup_gate.go`: forwardAuth →
|
||||
> `/__felhom_gate/auth`, handshake on `felhom.<domain>/__gate/start`, host-only gate cookie, key
|
||||
> `<data>/setup-gate.key`; the gate file is written BEFORE the first start; open = record, then remove the file) plus
|
||||
> R-710 (absent after_install record = not-run-yet for 30 min only; "I changed it"), R-709 (password fields off the
|
||||
> page), `generate: password:N:special`. Catalog `d0e7e2e`: gate on immich/n8n/audiobookshelf/uptime-kuma; mealie/wger/
|
||||
> calibre-web after_install (argv, never in code); romm/zipline notes; grafana R-708; wger R-712. Floor 0.280.0; both
|
||||
> demo boxes on it. R-707: 30 class-4 apps left. Report: `REPORT-login-gate-2026-09-29.md`.
|
||||
|
||||
> **2026-09-28 evening — operator rulings (decisions 44, 45):** 44 — demo-hp's restore test uses `nvme-scratch` (the
|
||||
> agent's storage role was granted there with the operator's word; proven). 45 — no app is published with a login a
|
||||
> stranger knows: generated first password via `after_install:` where the app's own CLI can set it, else the page
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# 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.
|
||||
@@ -1,26 +1,26 @@
|
||||
# STATUS — what works, what's broken, what's next
|
||||
|
||||
**Updated 2026-09-28 evening. Both demo boxes run controller 0.279.0 and host agent 0.137.0. Hub 0.125.0. New installs get golden 0.276.0 with agent 0.137.0.**
|
||||
**Updated 2026-09-29 morning. Both demo boxes run controller 0.280.0 and host agent 0.137.0. Hub 0.125.0. New installs get golden 0.276.0 with agent 0.137.0.**
|
||||
|
||||
**Decisions today** (yours, recorded): the HP box's restore test uses the big NVMe disk. No app goes live with a login a stranger knows.
|
||||
**Decisions today** (yours, recorded): I changed the HP box's two demo passwords. The "gate" was tested first, and built only because the test passed.
|
||||
|
||||
**What I did, and it worked.**
|
||||
- **claper and bookstack now get their own random first password.** The box sets it right after install and shows it on the app page. The old shared password no longer works. Tested on fresh installs; for claper also after a restore.
|
||||
- **Every app with a known default login now says so** on the install page and the app page: "This app starts with a known, shared password: … Change it right after the install."
|
||||
- **The HP box's full-system restore test works now, on the NVMe disk.** It needed one Proxmox permission, which you approved. The test passed in 9 minutes; the other disk was not touched.
|
||||
- **A running app whose backup holds no data now raises an alarm** for us and a sentence on its backup page. Yesterday's nextcloud case was silent.
|
||||
- **A debug button runs the night's backup chain now**, in the night's order. Tested on the scratch box.
|
||||
- **Removing an app "with its backups" now also deletes its off-site check copy.**
|
||||
- **ep0:** the test install's network link was already gone. Nothing to remove.
|
||||
- **The HP box's bookstack and calibre-web have new admin passwords.** They are in your credentials file, under names starting `DEMO_HP_`. The old shared passwords no longer work. Their pages no longer warn.
|
||||
- **The gate test passed.** A new app whose first visitor would become its admin is now closed to strangers until you set it up. A stranger sees a page that says "This app is waiting for its first setup. Sign in to the Felhom dashboard." You, signed in to the dashboard, go straight in. It adds about 2 ms.
|
||||
- **The gate opens by itself** when the app says it has an admin (immich, n8n, audiobookshelf: within 30 seconds of the setup). For an app that cannot say it, you press "Kész, beállítottam" on the app page (uptime-kuma).
|
||||
- **After it opens, nothing of it is left.** immich's phone-app login worked unchanged.
|
||||
- **Every app that started with a known password now gets its own random one**: mealie, wger and calibre-web joined claper and bookstack.
|
||||
- **Small fixes:** wger's login did not work from any browser (fixed). romm and zipline no longer show a login that does not exist. grafana refuses to start without its password. Installed apps' passwords are no longer inside the settings page's code; the eye button fetches them.
|
||||
- **New button on the app page:** "Megváltoztattam" / "I changed it", under a known default login.
|
||||
|
||||
**What is not done.**
|
||||
- **37 apps still start with a login a stranger can take.** 3 have a known default (calibre-web, mealie, wger). 34 let the first visitor create the admin. You chose to fix them over several sessions. The list and the route for each are written down.
|
||||
- **On the HP box, the installed bookstack and calibre-web still accept their default passwords.** I changed nothing there, as the brief said. Their pages now warn.
|
||||
- **romm's page shows a default login that does not exist.** Filed with the 37.
|
||||
- **The night watch did not run.** It was optional.
|
||||
- **30 apps still let the first visitor create the admin.** Each needs the gate switched on and tested. That is the next sessions' work, as you chose.
|
||||
- **About a dozen apps still allow open sign-up after the setup.** The gate does not change that. Written down.
|
||||
- **I did not test a restore of a gated app live.** It needs a whole-box backup, which test runs must not do. The code keeps the gate's state through a restore, and a test checks it.
|
||||
|
||||
**Rows.** 3 opened, 2 closed. The list went from 342 to 345 rows.
|
||||
**Costs you should know** (no decision needed): while an app is still closed, its phone app cannot reach it, and it stops answering if the controller is down. "Kész, beállítottam" trusts you: pressed too early, the app opens before you set it up.
|
||||
|
||||
**Rows.** 4 opened, 4 closed. The list went from 346 to 350 rows.
|
||||
|
||||
**What needs you.**
|
||||
1. **The installed bookstack and calibre-web on the HP box:** (A) I change their admin passwords on the box and show them to you (I recommend this; they are on the internet), or (B) leave them. If you do nothing, anyone who knows those defaults can log in to the two demo apps.
|
||||
2. **D4, the image copies, the Peti leftovers:** unchanged. If you do nothing, nothing changes.
|
||||
1. **D4, the image copies, the Peti leftovers:** unchanged. If you do nothing, nothing changes.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# demo-hp 9201, controller 0.280.0; ASCII fragment 'ismert, k'. The operator changed both passwords at 06:06Z (A1).
|
||||
BEFORE bookstack: sentence ('ismert, k') True | 'I changed it' press True | record None | bytes 44551
|
||||
BEFORE calibre-web: sentence ('ismert, k') True | 'I changed it' press True | record None | bytes 42903
|
||||
BEFORE romm: sentence ('ismert, k') False | 'I changed it' press False | record None | bytes 41354
|
||||
PRESS bookstack: 'I changed it' -> 200 {'data': {'recorded': True}, 'error': '', 'ok': True}
|
||||
PRESS calibre-web: 'I changed it' -> 200 {'data': {'recorded': True}, 'error': '', 'ok': True}
|
||||
AFTER bookstack: sentence ('ismert, k') False | 'I changed it' press False | record {'changed_at': '2026-09-29T07:21:06Z', 'by': 'household'} | bytes 43386
|
||||
AFTER calibre-web: sentence ('ismert, k') False | 'I changed it' press False | record {'changed_at': '2026-09-29T07:21:06Z', 'by': 'household'} | bytes 41756
|
||||
AFTER romm: sentence ('ismert, k') False | 'I changed it' press False | record None | bytes 41354
|
||||
# NOTE: the press was made by CC on the operator's ruling of 2026-09-29 (Part A). The record says by=household because the product has one presser; it changes only app.yaml's default_login, nothing in the app.
|
||||
@@ -821,7 +821,7 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server`
|
||||
| **R-707** | **[P2] 37 apps still start with a login a stranger can take (`09` §3 decision 45).** Audit of all 53 apps: `app-catalog-felhom.eu/FIRST-ADMIN.md` (class, fix route, status, measured or read). Open: **3 hard-coded defaults** — calibre-web (`admin / admin123`, measured working on demo-hp and 9202; its own `cps.py -s` route needs a generated password WITH a special character — our generator is letters+digits, a controller change), mealie (`changeme@example.com / MyPassword`), wger (`admin / adminadmin`); **34 open first-run screens** (the first visitor creates the admin: actualbudget, adventurelog, audiobookshelf, calcom, docmost, emby, ghost, gitea, gramps-web, home-assistant, homebox, immich, jellyfin, komga, n8n, navidrome, opengist, outline, papra, plant-it, radarr, rallly, recipe-importer, romm, seerr, sonarr, sparkyfitness, tandoor, termix, uptime-kuma, vikunja, wanderer, wishlist, zipline). **Stale notes:** romm's `default_creds` `admin / admin` answers 401 on demo-hp (like a wrong password) — the page now warns with a login that does not exist; zipline's looks stale too. **Measured on demo-hp 2026-09-28 (read-only):** bookstack's default still logs in on the INSTALLED app (the fix is for new installs; the page now warns). Each fix: route (a) env or (b) the app's own CLI/API via `after_install:`, proven on 9202 with the default failing and the generated password working; route (c) a page sentence. Several sessions (operator, 2026-09-28). **2026-09-29 (controller v0.280.0, catalog `d0e7e2e`):** every class-3 app fixed — mealie, wger, calibre-web by `after_install` (calibre-web with the new `password:24:special`), proven on 9202 fresh installs (`audits/login-gate-2026-09-29/D/`); the setup gate (decision 46, spike PASSED) built and live on immich, n8n, audiobookshelf (probes measured) and uptime-kuma (button) (`…/C/`); romm's and zipline's stale notes removed. **Left: 30 class-4 apps** — gate each (probe measured on 9202 where one exists — 11 upstream candidates listed in `…/B/B-VERDICT.md` §3; the button otherwise). | **OPEN — P2; owner: CC; 30 of 37 left** |
|
||||
| **R-708** | **[P3-LOW] grafana falls back to password `admin` when its admin field is empty.** `templates/grafana/docker-compose.yml:18` `GF_SECURITY_ADMIN_PASSWORD=${…:-admin}` (read 2026-09-28, the audit). Today the field is generated and required, so it is never empty on a normal install — but an edit, an import or a restore that drops the value would publish grafana with `admin / admin`. **Fix direction:** no default in the compose (`${GF_SECURITY_ADMIN_PASSWORD:?}` refuses to start instead). **Fixed 2026-09-29** (catalog `d0e7e2e`): `${GF_SECURITY_ADMIN_PASSWORD:?…}` — `docker compose config` with it empty or unset exits 1 naming R-708, set → 0 (`audits/login-gate-2026-09-29/D/D4-grafana-r708.txt`). | **CLOSED — 2026-09-29** |
|
||||
| **R-709** | **[P3-LOW] The deploy page writes the generated admin passwords of installed apps into its HTML.** `internal/web/templates/deploy.html` renders a `type: password` field's decrypted value into a disabled `<input value=…>` (read 2026-09-28; used by the proofs of R-702/R-707 to read the first password as the household sees it). `type: secret` fields got a fetch-on-demand reveal in R-254; `type: password` fields did not. The page needs a login, so this is exposure to a logged-in session's HTML (browser cache, a shared screen, a saved page), not to strangers. **Fix direction:** the R-254 reveal for password fields too. **Fixed in controller v0.280.0:** an installed app's password field renders empty with a reveal eye (`/stacks/<n>/auto-field/reveal` now serves `type: password` of an installed app, never a restore-generated one). Red-proofs RP14/RP15; live on 9202: mealie, wger, calibre-web — the revealed value is NOT in the settings page HTML (`…/D/D6-r709-live.txt`). | **CLOSED — 2026-09-29** |
|
||||
| **R-710** | **[P2-MEDIUM] An app installed before its template gained an `after_install:` is never warned about its default login.** MEASURED 2026-09-29 on demo-hp: bookstack's page carried no known-login sentence although its default `admin@admin.com / password` still logged in (the app was installed before the catalog added bookstack's `after_install` on 2026-09-28; the command never runs for an installed app). `internal/web/known_login.go` reads an ABSENT `after_install` record as "not run yet" for ever. Evidence `audits/login-gate-2026-09-29/A/A2-page-warning-after.txt`. Also: the page has no way to learn of a password the household changed by hand (the brief's Part A4). **Fix direction:** absent record + installed longer than the command's window = in effect; a household "I changed it" press recorded in `app.yaml`. | **OPEN — P2; owner: CC** |
|
||||
| **R-710** | **[P2-MEDIUM] An app installed before its template gained an `after_install:` is never warned about its default login.** MEASURED 2026-09-29 on demo-hp: bookstack's page carried no known-login sentence although its default `admin@admin.com / password` still logged in (the app was installed before the catalog added bookstack's `after_install` on 2026-09-28; the command never runs for an installed app). `internal/web/known_login.go` reads an ABSENT `after_install` record as "not run yet" for ever. Evidence `audits/login-gate-2026-09-29/A/A2-page-warning-after.txt`. Also: the page has no way to learn of a password the household changed by hand (the brief's Part A4). **Fix direction:** absent record + installed longer than the command's window = in effect; a household "I changed it" press recorded in `app.yaml`. **Fixed in controller v0.280.0** (RP13 red-proof): an absent record is "not run yet" only for 30 minutes after the install; the card has „Megváltoztattam" / "I changed it" (`app.yaml` `default_login`). **Live on demo-hp 2026-09-29:** after the delivery bookstack's page warned again (the positive control), then the press on both apps removed the sentence (`audits/login-gate-2026-09-29/A/A3-demo-hp-changed-it.txt`). | **CLOSED — 2026-09-29** |
|
||||
| **R-711** | **[P2-MEDIUM] About a dozen class-4 apps keep open sign-up after their first admin exists — the setup gate (decision 46) does not close that.** FOUND 2026-09-29 by the gate spike (`audits/login-gate-2026-09-29/B/B-VERDICT.md` F3). The gate decides who becomes the admin; once it opens, a stranger can still make an ordinary account on adventurelog, homebox, papra, plant-it, sparkyfitness, vikunja, wanderer, rallly, opengist, wishlist, termix, docmost (READ from `app-catalog-felhom.eu/FIRST-ADMIN.md`, not measured). **Fix direction:** per app, route (a) — disable sign-up after the first user (env or the app's own setting), measured on 9202. | **OPEN — P2; owner: CC** |
|
||||
| **R-712** | **[P2-MEDIUM] wger refused every browser sign-in behind traefik: "CSRF verification failed".** MEASURED 2026-09-29 on 9202 (live catalog wger 2.6): a POST to `/en/user/login` with the browser's `Origin: https://…` answered 403 — Django saw the request as http (no trusted proxy header) and no `CSRF_TRUSTED_ORIGINS`. Found while proving R-707's wger route. **Fixed** (catalog `d0e7e2e`): `CSRF_TRUSTED_ORIGINS=https://${SUBDOMAIN}.${DOMAIN}` + `X_FORWARDED_PROTO_HEADER_SET=True`; proven on a fresh install: the generated password signs in (302) with the https Origin (`audits/login-gate-2026-09-29/D/D2-live.txt`). | **CLOSED — 2026-09-29** |
|
||||
| **R-713** | **[P3-LOW] claper's `after_install` pastes the household's password into Elixir code, and the controller does not refuse a value that would break such code.** FOUND 2026-09-29 by a background security review of the drill commit (mealie/wger had the same shape and were changed to pass the password as `sys.argv[1]`). claper's `bin/claper rpc '… "${ADMIN_PASSWORD}" …'` has no argv: a household-typed password with `"` or `#{` breaks the command (recorded as failed; the page then warns) or changes the Elixir it runs — inside the household's own claper container, as that app. The generated value (letters + digits) is safe. **Fix direction:** (1) controller: `expandAfterInstall` refuses a value holding a quote, a backslash, `$`, `{`, `}`, a backtick or a newline — or a declared per-field encoding; (2) claper: read the value some other way (a file the command reads, or `System.get_env` from a one-shot env). | **OPEN — P3; owner: CC** |
|
||||
|
||||
Reference in New Issue
Block a user