hub v0.153.0: off-site and PBS-DR re-issues session only too (R-1059 = A, operator 2026-10-15); R-1059 closed
gates / gates (push) Successful in 6m41s

Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
This commit is contained in:
2026-10-15 13:33:25 +02:00
parent 368ee9878d
commit 00686789e1
11 changed files with 42 additions and 22 deletions
+1
View File
@@ -36,6 +36,7 @@
| Row | What | Closed | Evidence |
|---|---|---|---|
| **R-1059** | **A token with „Off-site: write" could re-issue a household's off-site or PBS-DR credential and stop its backup for a while.** (P3) | CLOSED 2026-10-15 — operator „A" (session only); built in hub v0.153.0 with decision 252: `/configs/{id}/offsite-reissue` and `/pbsdr-reissue` are session only. Filed and closed the same day. | `TestToken_CredentialAndLinkRoutesAreSessionOnly` (red-proof `audits/hub-tokens-2026-10-15/red-proof/15-offsite-reissue.txt`); `05` §17.3 |
| **R-1055** | **A stolen „Customers: write" token could take over a household's dashboard: change the e-mail, then resend the claim code.** (P3) | CLOSED 2026-10-15 — operator ruling 13:20 (A, `09` §3 decision 252); **built in hub v0.153.0 (not deployed — the operator's „yes" first)**: claim resend, customer create, customer edit and create-config are session only, with the self-bind link, regen-password and appliance bind already so. Full text: `git show 5d78569b:documentation/backlog/OPEN-ITEMS.md` | `TestToken_CredentialAndLinkRoutesAreSessionOnly` (red-proved twice: `audits/hub-tokens-2026-10-15/red-proof/13-claim-resend.txt`, `14-config-edit.txt`); `05` §17.3, §17.5 |
| **R-1051** | **CC could not raise the global floor without the operator's own password.** (P3) | CLOSED 2026-10-15 — hub v0.152.0 deployed (operator „yes"); the operator created the token „cc-releases" (2026-10-15 13:06, wider than the task's two scopes: also customers/hosts read, offsite and os read+write — the operator's choice). Live with the token: a no-op floor save 0.313.0/MinAgent 0.155.0 → 303 `flash=floor_set`, read back from the hub DB with its WAL; the vouch → 403 `missing scope vouch:write`; the customer page and a recovery-credential reveal → 403 `session only`. The `felhom-build-deploy` skill's floor step and `gitea-image-prune.sh` now use the token. Full text: `git show e92501f5:documentation/backlog/OPEN-ITEMS.md` | `audits/hub-tokens-2026-10-15/C3-token-live.txt`, `deploy.txt`; `05` §17; `09` §3 decision 251 |
| **R-952** | **Nothing failed when a new hub route was added without deciding who may call it.** (P3) | CLOSED 2026-10-15 — **built in hub v0.152.0**, both surfaces: `/api/v1` — `internal/api/routes.go` names each route's caller (public with a reason, customer, host, global, passphrase, appliance, recovery), pinned to the switch from source; every non-public route with no credential, for a household and host that exist, answers 401/403. Web — `internal/web/routes.go` classifies every route for access tokens, pinned the same way. Full text: `git show e92501f5:documentation/backlog/OPEN-ITEMS.md` | `TestAPIRouteTable_MatchesSwitch`, `TestAPIRoutes_NoCredential`, `TestRouteTable_MatchesServeHTTPSwitch` (each red-proved, `REPORT-hub-tokens.md`); `05` §17.3–17.4 |
+1 -2
View File
@@ -197,7 +197,7 @@ stopping line that lies.
| **R-332** | Storage & devices | P3 | **The new Hiba-from-counters path has never fired on real hardware.** v0.215.0's whole point is a verdict the product could not previously reach, and it is proven only against the committed fixture's values in unit tests (12 scenario groups, 11 of 12 red-proofs failing as required). The live validation on demo-hp proved the **negative** — three healthy disks still read Rendben across the deploy, no false alert — and the **severity wire** end to end, but no live disk has actually reached Hiba. **This is the honest gap and it must not be closed by pointing at the fixture tests**: the drive that produced the fixture is in DooPlex, which is Tier 2 and never a drill target, and the demo boxes are all-flash and healthy | **WATCHING — NEW 2026-08-14, NARROWED same day.** One item originally in this gap is now PROVEN LIVE: the **persisted state surviving a controller restart**. The v0.215.0→v0.216.0 redeploy destroyed and rebuilt the container, and the new one read back a `changed_at` written by the PREVIOUS version (`2026-08-14T07:23:14.640216851Z`, still intact at 09:31:35Z) instead of re-baselining — Scenario L on real hardware, not just the production-path unit test. **What remains unproven is the verdict itself, plus the stronger restart half: an already-ALERTED disk not re-alerting** | a real degrading disk, or an injection harness | **Closing condition:** a live disk reaching Hiba from counters, OR a deliberate injection through the REAL pipeline (agent `/disks` → controller check → hub event), not a hand-set verdict | CC |
| **R-331** | Storage & devices | P4 | **Disk health Phase 3 — growth-rate detection, and retiring the static 64.** The v0.215.0 count backstop (64 unreadable sectors → Hiba) is **a judgement from ONE drive**: the observed benign excursion peaked at 16 and cleared inside an hour, and the terminal run passed 64 at 13 Aug 11:28 and never came back. It is deliberately a backstop BEHIND the sustain rule, not the primary signal, but it is still a magic number tuned on a single sample and it will be wrong for some drive. With Phase 2's history the box can ask the question that actually matters — *is this count climbing, and how fast* — which distinguishes a drive with eight stable aging sectors from one adding forty a day, something no static threshold can do. Revisit 64 when that exists | **READY (M) — NEW 2026-08-14** | R-330 | Growth-rate rule over persisted samples; re-derive or delete the static 64 | CC |
## Security & access — 23 rows (P1 2, P2 3, P3 12, P4 6)
## Security & access — 22 rows (P1 2, P2 3, P3 11, P4 6)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
@@ -223,7 +223,6 @@ stopping line that lies.
| **R-1050** | Security & access | P4 | **Raise HSTS to one year on or after 2026-11-15, if no https trouble was reported.** `09` §3 decision 249 (D2 = A, operator 2026-10-15): HSTS stays `max-age=86400` now; after a month with no https trouble it becomes one year (`31536000`); never preload, never `includeSubDomains`. The value is `HSTSMaxAgeSeconds` in `felhom-controller/controller/internal/infra/infra.go`, pinned by `TestRenderForwardedHeaders_HTTPSOnly`. „Trouble” = any report of a name that will not open, or a box answering http after the floor. | **WATCHING** — filed 2026-10-15 | the date | On or after 2026-11-15: read the register and the operator's mail for https trouble since 2026-10-15; none → a controller release with one year (test updated), bake → vouch → floor | CC |
| **R-1054** | Security & access | P3 | **The hub's operator/global API key is still one bearer that can do everything on `/api/v1`, including reading every box's break-glass root password (`GET /admin/hosts/{id}/recovery-credential`).** Hub v0.152.0 gave the web surface scoped tokens (`05` §17) but tokens are refused on `/api/v1` by design. Who uses the global key today is not listed in one place. Fix: list its consumers; move each operator use to a scoped token on the web side (or a token class on the API), and keep the global key only where a box path needs it — or retire it. | OPEN — filed 2026-10-15 by the token build | — | List the global key's consumers (pod env, scripts, runbooks) | CC |
| **R-1057** | Security & access | P3 | **A restore of a REMOVED gated app comes back without its setup gate, even when the backup was taken before the household's setup — so the app returns open, with no account.** Measured 2026-10-15 on 9202 with Prowlarr (`audits/prowlarr-2026-10-15/box/run3/restore.txt`): installed (gated, no account made), the guarded Update's per-app backup, remove keeping the backups, the household's restore button — afterwards a stranger's `/ping` through traefik answered 200, where before the remove it answered 401 (the gate). This follows the documented rule (`felhom-controller` `internal/stacks/life_records.go` `carryLifeRecords`: no prior record = no gate, „the data comes back with its admin"); the rule's assumption fails for a backup made before the setup. For a first-visitor app (Prowlarr, n8n, …) the first stranger sets the sign-in; for Prowlarr `/initialize.json` also hands out the API key (seen before the setup in the install window — NOT measured after this restore). Needs: the household to install, let a backup run before finishing the setup, remove, restore. | **OPEN** — filed 2026-10-15 by the Prowlarr walk | — | Record the gate's state in the per-app backup and re-close it on restore when it was closed then; red-proof with this walk (`tools/walk_restore.py`) | CC |
| **R-1059** | Security & access | P3 | **A token with „Off-site: write" can re-issue a household's off-site or PBS-DR credential** (`/configs/{id}/offsite-reissue`, `/pbsdr-reissue`: the box password is reset, a fresh one-time secret is staged for the box). The token holder never sees the secret, but can break the household's backup until the box picks the new one up. Decision 252 covered „Customers: write" only; the operator's own token „cc-releases" holds `offsite:write`. | WAITING-ON-OPERATOR — filed 2026-10-15 by the decision-252 build | operator decision | Decide: keep the two re-issues in Off-site write, or session only (two lines in `hub/internal/web/routes.go`) | operator |
## Box system & updates — 13 rows (P2 1, P3 9, P4 3)