Checklist 3.10 (since 2026-10-02): the visitor's address — read from the right, never the leftmost; REUSE pattern for the router reset (R-753)
gates / gates (push) Successful in 2s

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:
2026-10-01 21:31:36 +02:00
parent 50e4fb42df
commit 9b3b859eef
3 changed files with 5 additions and 2 deletions
+3 -2
View File
@@ -83,6 +83,7 @@ well-formed, and may say `open`.
| 3.7 | 2026-10-01 | The household can change its password and add family members; the page says how | 9202 | `add_people` copy |
| 3.8 | 2026-10-01 | Secrets pass to commands as arguments, never inside program code | review `after_install` | claper pasted the password into Elixir code (R-713); security review 2026-09-29 |
| 3.9 | 2026-10-01 | **Sign in the way each client does, through traefik over https**: the browser form (with its https `Origin` and CSRF cookie) AND every API login route a phone/desktop app uses — right password works, wrong refused | 9202, `curl` with the headers a browser sends; the API route from 0.7 | wger refused every browser sign-in behind traefik (CSRF, R-712); wger's API login 500 (R-737) |
| 3.10 | 2026-10-02 | **The visitor's address**: how the app reads it (from the RIGHT with a trusted-proxy list / a fixed count / the LEFTMOST `X-Forwarded-For` / not at all) and what it decides with it (log, lock, "local network" rights). A leftmost reader whose address decides anything carries the router reset `<router>-xff`; a right-walking reader with a setting gets `172.16.0.0/12` trusted | read the app's client-address function at the pinned tag; then on 9202 a forged leftmost `X-Forwarded-For` through the SIMULATED tunnel (a container at `172.16.253.2`, Part A's method) must not become the address the app logs or locks | since controller v0.286.0 traefik keeps the tunnel's chain, whose leftmost entry a stranger writes (R-753); 19 catalog apps read the leftmost (`felhom.eu/documentation/audits/visitors-2026-10-01/A/sweep/`) |
## 4. Health
@@ -148,11 +149,11 @@ well-formed, and may say `open`.
| 0 Fit | 9 |
| 1 Images, start command, database | 9 |
| 2 Storage and backup | 8 |
| 3 Accounts and strangers | 9 |
| 3 Accounts and strangers | 10 |
| 4 Health | 5 |
| 5 Resources | 5 |
| 6 Updates | 5 |
| 7 Mail | 1 |
| 8 Text and listing | 5 |
| 9 Sign-off | 4 |
| **total** | **60** |
| **total** | **61** |
+1
View File
@@ -24,6 +24,7 @@ Templates are config; the few script helpers other scripts must REUSE, never re-
| **Known default login → `after_install:`** (decision 45, controller ≥ 0.279.0) | `templates/bookstack/.felhom.yml` (`ADMIN_PASSWORD` field + `after_install:` block); `FIRST-ADMIN.md` for every app | An app that starts with a known admin login gets a generated `type: password` field (`generate: "password:24"`, `locked_after_deploy: true`) and ONE `after_install: {service, env: [ADMIN_PASSWORD], command: [...], success: "<marker the output must carry>"}` through the app's OWN CLI, run once after a FRESH install. Keep `app_info.default_creds` — the page hides it once the command succeeded and warns while it is in effect. **Prove on 9202 (drill catalog) before live: the default fails, the generated password works, a wrong one fails.** TRAPS: `success:` is required because a CLI can exit 0 on an error (claper's `rpc`); **pass the password as its own argument, never inside program code** (`sys.argv[1]` — mealie, wger; security review 2026-09-29); a special-character policy uses `generate: "password:24:special"` (controller ≥ 0.280.0, calibre-web); a Hungarian first-steps change needs `check-copy-i18n.py --capture-freeze`. |
| **Open first-run screen → `setup_gate:`** (decision 46, controller ≥ 0.280.0) | `templates/n8n/.felhom.yml` (probe), `templates/uptime-kuma/.felhom.yml` (no probe → the household's button); `FIRST-ADMIN.md` | `setup_gate: true` + optional `setup_done_probe: {url: http://<container_name>:<port>/<path>, field: <dotted.json.path>, done: "<text>"}` | TRAPS: the probe must FLIP on the setup — measure it before and after on 9202; an app with open sign-up after setup (R-711) is not closed by the gate; `url` is read on the docker network, so it names the container, not the subdomain. |
| **Open sign-up after the setup → `signup_block:`** (decision 47, controller ≥ 0.281.0) | `templates/opengist/.felhom.yml`, `templates/calcom/.felhom.yml` | `signup_block: "<traefik matcher>"` + `app_info.add_people` (hu) / `i18n.en.app_info.add_people` | TRAPS: block the app's API sign-up call, not only the page; an app's own invite link often uses the same address (the household's 15-minute window covers it); `add_people` is copy — `--capture-freeze`. |
| **The visitor's address — read from the RIGHT, never the leftmost** (R-753, controller ≥ 0.286.0) | `templates/docmost/docker-compose.yml` (the reset), `templates/home-assistant/` (a right-walking reader) | traefik keeps the tunnel's chain: `<client-written…>, <real visitor>, 172.16.253.2`; the LAN gives one entry. An app that reads the LEFTMOST entry gets the router reset: `traefik.http.middlewares.<router>-xff.headers.customrequestheaders.X-Forwarded-For=` + `traefik.http.routers.<router>.middlewares=<router>-xff` (defined on the router's own container) — it then reads X-Real-Ip or its peer. A right-walking reader gets `172.16.0.0/12` as its trusted proxies. A FIXED count is wrong for one of the two paths — leave count readers alone. **Never turn on** a leftmost switch: glance `proxied`, karakeep `RATE_LIMITING_ENABLED`, vaultwarden `IP_HEADER=X-Forwarded-For`, PocketBase `UseLeftmostIP`, navidrome's reverse-proxy whitelist, Plex `ALLOWED_NETWORKS`. Sweep of all 56: `felhom.eu/documentation/audits/visitors-2026-10-01/A/sweep/`. Checklist 3.10 |
| **The app's own sign-up switch → `after_setup:`** (decisions 47/49, controller ≥ 0.282.0) | `templates/homebox/` (compose `HBOX_OPTIONS_ALLOW_REGISTRATION=${SIGNUP_OPEN:-true}` + `.felhom.yml` `after_setup.env`) | compose default OPEN; `after_setup: {env: {SIGNUP_CLOSED: "true"}}` | TRAPS: the default must be OPEN (an installed app is unchanged by the catalog); several apps' switch also refuses the household's FIRST account, so never set it at install; an app with no env switch (opengist, wishlist) keeps the block alone — make that block case-insensitive. |
| **A same-tag security fix → a RE-TEST step** (`09` decision 52, 2026-09-30) | `scripts/retest-floating.py` (the ONE monthly command), `scripts/retest_box.py` (the box venue), `scripts/box_walk.py` (the 9202 client), `upgrade-test.py --retest` + `--write-ladder` | An entry whose `from` == `to`, with `digest_from` (the tested digest it started from) and `box_evidence`. `--dry-run` lists; `--engines-only` is the ruled start. Runbook `felhom.eu/documentation/runbooks/monthly-floating-retest.md`. TRAPS: never write one by hand (the gates refuse no new digest, a digest the registry stopped serving, a missing box proof, a `digest_from` that is not the previous entry's digest); `image_digest.resolve` IGNORES a `@digest` in its argument — ask the registry by manifest for a digest. |
| **A bench-only environment override** (R-739) | `upgrade-test.py` `BENCH_ENV_OVERRIDES` | Only for an app that cannot run on the bench at all (wanderer: its web server calls the DB at the public https name). Every verdict carries `bench_overrides`. Never a template change. |
+1
View File
@@ -48,6 +48,7 @@ rows; do not add free-form lines between them.
3.7 | open | not started: The household can change its password and add family members
3.8 | open | not started: Secrets pass to commands as arguments, never inside program code
3.9 | open | not started: Sign in the way each client does, through traefik over https: the browser form (with …
3.10 | open | not started: The visitor's address: how the app reads it, and what it decides with it
4.1 | open | not started: Compose healthcheck of the family the IMAGE has (inspected, one tool per run), …
4.2 | open | not started: The controller probe dials what the compose healthcheck dials
4.3 | open | not started: Healthy within start_period on a cold first start (incl. one-time imports), with no …