Doc 14: turning the machine off (R-998) — path, guards, the hub's 14-day hold, the threat; F11; R-1000 filed
gates / gates (push) Successful in 6m40s

03 §6 lists /guest/reboot and /host/poweroff; 13 counts 38 local-API routes and names the new
power of attacker (e). Hub CHANGELOG corrected: os_ring0_stalled is NOT held (a fleet rollout
alarm, on purpose). R-983: the GET reboot fixed (six remain). R-1000: the website FAQ line.
This commit is contained in:
admin committed 2026-10-13 20:36:42 +02:00
1 parent e8c8fc309b
commit 81e64c6fe8
7 files changed
+121 -7

No files matched your search

@@ -217,6 +217,7 @@
| <a id="f7"></a>[F7](capability-map-detail/F.md#f7) | Always-on debug rings + on-demand log-bundle pulls with TTL/custody | controller v0.116, agent v0.83, hub v0.46 | **PROVEN-LIVE** | — | `audits/CAMPAIGN-3-2026-07-11.md` (2026-07-11) |
| <a id="f8"></a>[F8](capability-map-detail/F.md#f8) | Operator alerting (Healthchecks → monitoring@felhom.eu) | k3s, Resend | **IMPLEMENTED** | no corpus validation doc | operator infra; no evidence document |
| <a id="f9"></a>[F9](capability-map-detail/F.md#f9) | Backup-deadline alerting (`expected_backup_missed`) is ANCHORED — absence of signal is UNKNOWN, not failure | hub v0.75.0 | **IMPLEMENTED** | — | `audits/DIAG-backup-missed-2026-07-26.md` (2026-07-26); R-82 |
| <a id="f11"></a>[F11](capability-map-detail/F.md#f11) | The household turns the whole machine off („A gép kikapcsolása"); the hub holds every silence alarm for 14 days and shows KIKAPCSOLVA | agent v0.155.0, hub v0.149.0, controller v0.310.0 | **PARTIAL** | built and tested; live proof on the N100 pending | `architecture/14-host-power-off.md`; `audits/poweroff-2026-10-13/`; R-998 |
| <a id="f10"></a>[F10](capability-map-detail/F.md#f10) | A box that is not always on (off at its backup time) makes the missed night up once when it comes back; the household sees a banner; the operator gets the missed-backup alarm, down or not | controller v0.295.0, hub v0.134.0, agent v0.145.0 | **PARTIAL** | catch-up and banner live; R-872 alarm by tests; suspend not measured | `audits/catchup-2026-10-05/partA/` (2026-10-05); R-872 |
## G. Fleet & operator (hub)
+7 -2
View File
@@ -264,9 +264,14 @@ destructive → the §4 gate → refused **`pending_signature`** (the operator-s
drive — the 8C analog of self-scoping. **Status: implemented** (agent v0.12.0 `internal/localapi` +
`internal/storage`; controller v0.37.0 `internal/web/agent_disk_handlers.go`).
- **Guest restart:** `POST /guest/reboot` — `pct reboot` of *this* guest (the dashboard's „A Felhom újraindítása").
- **Host power-off (R-998, agent v0.155.0):** `POST /host/poweroff` — the household's „A gép kikapcsolása": this guest
shut down, then the host powered off. Guarded (10 min after boot, one per boot, no other guest or VM running, no
backup/restore/update/drive job) — the whole design, its hub hold and its threat: `14-host-power-off.md`.
Note what is *absent*: nothing here lets a controller touch **another guest**, the **host** beyond
this narrow disk surface, or **restore-overwrite**; and within the disk surface, **data-destructive**
power stays operator-signed (§4). Destructive/cross-guest power stays operator-signed.
this narrow disk surface **and the guarded power-off above**, or **restore-overwrite**; and within the disk surface,
**data-destructive** power stays operator-signed (§4). Destructive/cross-guest power stays operator-signed.
A controller can only `POST /rollback` (or snapshot/backup) **its own** guest — the agent maps
token → guest and authorizes per guest, so a compromised controller's blast radius is
@@ -47,7 +47,7 @@ scripts are held with the review's working notes.
|---|---|---|---|
| controller | 231 (+8 in setup mode) | 21, of which 7 change state, each behind its own password, code or token | port 8080 on the guest's Docker networks (not published); mail relay 2525/2465/2526 |
| hub | 119 (1 health, 53 API, 65 web) | appliance register (rate-limited, by design), re-enroll (only while the operator has armed recovery mode), login, health | one ingress, path `/`, geo filter (HU, IE, LAN) |
| agent | 37 local-API routes | 0 | local API on the island bridge; WireGuard; poke (tunnel only); felhom-sshd (tunnel only, measured) |
| agent | 38 local-API routes (`POST /host/poweroff` added 2026-10-13, R-998) | 0 | local API on the island bridge; WireGuard; poke (tunnel only); felhom-sshd (tunnel only, measured) |
**Outbound fetches that bring code or config onto a box:** the catalog (git, `main`), app images, the four infra
images, the controller image (by tag), `controller.yaml` from the hub, the agent binary and config bundle (signed),
@@ -59,7 +59,7 @@ OS packages (exact versions, apt-signed), the installer script (by tag, R-980).
|---|---|---|
| end-user ↔ apps | holds, with gaps | traefik routing and header stripping hold; homepage has the Docker socket and no gate (R-981) |
| customer ↔ controller UI | partly | bcrypt and session hold; small gaps (R-983); family sessions survive a reset (R-976) |
| controller ↔ agent | partly | every route bound to the caller's guest; a compromised controller can still flush restore points (R-973) and void the recovery code (R-978) |
| controller ↔ agent | partly | every route bound to the caller's guest; a compromised controller can still flush restore points (R-973), void the recovery code (R-978), and — since agent v0.155.0 — switch the whole machine off once per power-on, holding the hub's silence alarms for up to 14 days (R-998; mitigations and residual risk: `14-host-power-off.md` §5) |
| agent ↔ hub | holds for destructive ops | signed jobs verified in the documented order; unsigned spec has no size bounds (R-984) |
| controller ↔ hub | **did not hold until 2026-10-12** | the global key was public (R-950, rotated) and three routes needed no key (R-951, fixed); per-box keys are bound to their box (R-955) |
| box ↔ PBS | holds as written | ciphertext only; the zero-knowledge wording is stronger than a live box allows (R-985) |
@@ -0,0 +1,88 @@
# 14 — Turning the machine off: „A gép kikapcsolása" (R-998)
> **What this is:** the household's way to switch the whole machine off — the path, the guards, what the hub does, and
> the threat it adds. Written 2026-10-13 with the build (agent v0.155.0, hub v0.149.0, controller v0.310.0).
> Spike: `documentation/audits/poweroff-2026-10-13/`. Marks as in `01`: **[DESIGN]** a decision; **[FACT]** read in a
> named file or measured.
## 1. Why
**[FACT]** Before this, nothing could turn a Felhom machine off: „Kiszolgáló újraindítása" restarted only the guest
(controller `HandleServerReboot` → agent `POST /guest/reboot` → `pct reboot`). A household moving house could only pull
the plug. **[FACT]** Measured on the N100 (spike Part A): a clean guest shutdown (12 s) and `systemctl poweroff` work;
the plug then reads 0.0 W; the hub stayed silent and „ONLINE" for 18 minutes, and by code alarms at 45 min
(`host_stale`), 90 min (`host_down`) and every 05:00 (`expected_*_missed`); the N100 did **not** start when the plug
came back (BIOS „last state", inferred), its power button did (dashboard in 42 s).
## 2. The path
| Step | Where | What |
|---|---|---|
| 1 | controller `settings_system.html` | card „A gép kikapcsolása"; the household types KIKAPCSOLÁS / TURN OFF |
| 2 | controller `POST /api/server/poweroff` (`internal/web/poweroff_handlers.go`) | dashboard login (`RequireAuth` + CSRF), POST only, the word again, the controller's own backup/restore/app-update check, the `host_poweroff` feature gate (MinAgent 0.155.0) |
| 3 | agent `POST /host/poweroff` (`internal/localapi/hostpoweroff.go`) | `withGuest` token scope; the guards (§3); the record (`internal/poweroff`) |
| 4 | agent → hub | a host report carrying `deliberate_off.requested_at`, waited for until the hub ACCEPTED it (≤ 60 s, `Loop.SetReportedHook`) |
| 5 | agent | `pct shutdown <vmid> --timeout 120`, then `systemctl poweroff` (two exact sudoers vectors, `FELHOM_POWEROFF`) |
| 6 | hub (`store/deliberateoff.go`) | `hosts.deliberate_off_at`; silence alarms held 14 days (§4) |
| 7 | the household | presses the machine's power button; the first report of the new boot has no `deliberate_off` → the hub clears it |
## 3. The guards (agent) — each refuses with a stable `reason`, each fails closed
| Guard | Reason | Test (`hostpoweroff_test.go`) |
|---|---|---|
| first 10 min after boot | `too_soon_after_boot` | `TooSoonAfterBoot` |
| one accepted request per boot (file keyed by `/proc/stat` btime) | `already_requested_this_boot` | `SecondRequestSameBootRefused`, `poweroff/state_test.go` |
| any other guest or VM running (`/proc`: `lxc-start -n N`, `kvm -id N`) | `other_guests_running` | `RefusedWhileOtherGuestsRun`, `RunningGuestsFromProc` |
| heavy-operation gate held (backup, restore test, OS step, sweep, trim) | `backup_running` / `restore_test_running` / `os_update_running` / `host_busy` | `RefusedDuringBackup` |
| backup job, controller swap, drive format, NAS verify, escrow ceremony, Proxmox vzdump | `backup_running` / `controller_update_running` / `drive_operation_running` / `escrow_ceremony_running` | `RefusedDuringVzdump`, `FailsClosedWithoutTaskReader` |
| after acceptance: the heavy gate is TAKEN and kept; before each step the jobs and other guests are read again — a job waits ≤ 15 min, another guest cancels at once, a job still running cancels | (log) | `JobStartedAfterAcceptance`, `OtherGuestStartedAfterAcceptance` |
**[DESIGN]** A cancelled power-off leaves the box on (gate released) — better a box that did not switch off than a
backup, update or format cut in the middle. **[DESIGN]** A failed guest shutdown does not stop the power-off: Proxmox's
own shutdown stops a still-running guest before the host goes down.
## 4. The hub's hold — **[DESIGN]** 14 days (operator default, `store.DeliberateOffHold`)
Held while the box is silent AND inside the hold — every alarm a switched-off box would raise, named from the code:
| Alarm | Where | How it is held |
|---|---|---|
| `host_stale`, `host_down` | `monitor/host_staleness.go` | state `off` |
| `node_stale`, `node_down` | `monitor/staleness.go` | state `deliberately_off` |
| `expected_backup_missed`, `expected_dbdump_missed` | `monitor/deadline.go` | skipped (state, and the host row) |
| `offsite_stale`, `offsite_escrow_pending` | `monitor/offsite.go` | customer skipped |
| `restore_test_stale` | `monitor/restoretest.go` | customer skipped |
| `os_update_stale`, `agent_behind`, `os_config_bundle_behind`, `os_reboot_needed`, `os_not_covered` | `osupdates/service.go` | host filtered |
| the kernel „tonight your box restarts" mail | `osupdates/kernel.go` | host filtered |
**Not held, on purpose:** `os_ring0_stalled` (`osupdates/service.go`, `ring0Hosts`) — it is about the FLEET's update
rollout, not about this box: a ring-0 box switched off stalls the rollout, and the operator should know.
After the hold, still silent: `host_down` + `node_down` once, with a text naming the household power-off. Back on inside
the hold: no `*_recovered` mail. Pages: KIKAPCSOLVA and „Kikapcsolva a háztartás által, T". A customer is held only
when EVERY host of it is held; a future `requested_at` (the box's clock) is cut to the hub's now. Tests:
`monitor/deliberate_off_test.go` (each with a control that alarms), `api/deliberate_off_test.go`,
`web/deliberate_off_view_test.go`.
## 5. The threat it adds — attacker (e), a compromised controller (`13-threat-model.md`)
**[FACT]** `/host/poweroff` is the first local-API route whose effect reaches beyond the caller's own guest: a
compromised controller can now switch the whole machine off, and **nobody can switch it on remotely**. And it can
choose to: its power-off holds the hub's silence alarms for 14 days.
| Mitigation | Bound |
|---|---|
| one request per boot, none in the first 10 min after boot | no power-off loop; one outage per physical power-on |
| refused while another guest or VM runs | cannot take down a neighbour's guest (demo-hp, multi-guest hosts) |
| refused during backup/restore/update/drive jobs; re-checked before each step | cannot cut a job in the middle |
| the hold ends at 14 days; one machine of two does not hold the customer | the silence is bounded |
**Residual risk, named:** a compromised controller can switch a single-guest household box off once per power-on and
silence the hub for up to 14 days. The household sees a dark machine; the operator sees „Kikapcsolva a háztartás által"
— not an alarm. Accepted for a household product whose controller is already trusted with the household's data; the
alternative (an out-of-band confirmation) needs a second channel the box does not have.
## 6. Not done here
- The website FAQ line („to turn it off: Beállítások → Rendszer → A gép kikapcsolása") — a later website task (R-1000).
- The BIOS „after power loss" value of the N100 was not read (no monitor); the household text promises only the button.
@@ -177,3 +177,22 @@
**Gap / roadmap (as written):** R-872 live; a suspend never measured
---
## F11 — The household turns the whole machine off („A gép kikapcsolása"); the hub holds every silence alarm for 14 days
*Map row: [F11](../00-capability-map.md#f11).*
**Scenario:** a household moving house or going away presses „A gép kikapcsolása" (Beállítások → Rendszer), types
KIKAPCSOLÁS; the guest shuts down, then the host; the hub shows KIKAPCSOLVA and raises no outage alarm for 14 days;
the machine's power button brings it back, and the hub clears the state.
**Components:** agent v0.155.0 (`POST /host/poweroff`), hub v0.149.0 (`hosts.deliberate_off_at`, the hold), controller
v0.310.0 (the card, `POST /api/server/poweroff`).
**Status:** PARTIAL — built and tested (each guard red-proofed); live proof on the N100 pending.
**Evidence:** design `14-host-power-off.md`; spike `audits/poweroff-2026-10-13/`.
**Gap / roadmap:** the website FAQ line (R-1000).
---
+3 -2
View File
@@ -112,10 +112,11 @@ match what the reader sees is how an instrument stops being believed (R-421). It
**nothing was proven on the day this line was drawn** and a stopping line that moves a status is a
stopping line that lies.
## Install & onboarding — 8 rows (P3 6, P4 2)
## Install & onboarding — 9 rows (P3 6, P4 3)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
| **R-1000** | Install & onboarding | P4 | **The website does not say how to turn a Felhom machine off.** Since controller v0.310.0 (R-998) the dashboard has „A gép kikapcsolása" (Beállítások → Rendszer); the FAQ still has only the power-cut line. Text to add (HU/EN): turn it off there; switch it on again with the power button; the hub stays quiet for 14 days. The plug trick is not mentioned to households. | **READY** — filed 2026-10-13 | — | One website task: one FAQ entry per language, `site_gates.py` | CC |
| **R-980** | Install & onboarding | P3 | **A person cannot check that the installer is ours: the ISO's first boot runs the install script from a movable tag with no hash or signature, and the ISO checksum sits on the same website as the download.** Fix: a signed manifest (or signature) checked by the ISO before it runs the script, and the public key printed in the guide. Security review 2026-10-12 (`audits/security-review-2026-10-12/`); detail held off-git for the operator (`/mnt/5_hdd/felhom.eu/evidence/security-review-2026-10-12-PRIVATE/`). | OPEN — design-proposal | — | Design note, then build | CC |
| **R-250** | Install & onboarding | P3 | **A customer create can fail fail-closed because the host-key scan ladder is shorter than the DNS/AAAA settle time.** Found 2026-08-07 creating the fifth walk's venue. `POST /configs/new` with off-site enabled provisions the Storage Box sub-account and then scans its SSH host key to pin it — **fail-closed by design** (`offsite.go:111-121`, *"don't serve a descriptor the controller can't verify"*), with `defaultScanBackoff` = 2+4+8+16+30 ≈ **60 s**, sized by its own comment to *"the observed DNS propagation lag"*. **Measured, both halves:** the first create exhausted the ladder — five `no such host`, then `dial tcp [2a01:4f8:bacc:2:200::d30]:23: connect: network is unreachable` (the name had just begun resolving, **AAAA-first, into a pod with no IPv6 route**) — and the hub logged `[ERROR] offsite provision for walk5`. An **identical second POST succeeded** ~70 s later on its own final rung (`shared already provisioned … (subaccount 285351)`). **Total settle ≈ 100 s against a 60 s budget.** The endpoint was never the problem: verified afterwards from both the node and the hub pod, by name, `SSH-2.0-OpenSSH_9.6p1`. **Two distinct things are wrong and should not be merged:** (1) the budget is sized against DNS *existence*, but what actually bit is the **AAAA-before-A** window, a different and longer phenomenon — lengthen the ladder and/or prefer the A record for the scan dial; (2) **the operator is told nothing actionable** — the create fails with a generic error, the remedy is "press it again", and nothing says so. The retry is genuinely safe (idempotent on the label; confirmed afterwards — `one_time_secrets` 1, one sub-account, no double-provision) **but that safety is invisible to the person deciding whether pressing again will double-charge them.** **Severity LOW-MEDIUM:** self-clearing, no data at risk — but it is the first thing a new customer's provisioning does and it fails looking like an outage. | **VERIFY** — **2026-10-12: hub 0.147.0 live** (S29, decision 224): the host-key scan dials IPv4 first, waits ~2 min (was ~1), and when it still runs out says the store was created and it is safe to press Save again (`TestScannerDialsIPv4First`, `TestScanOutOfRetriesSaysSafeToPressAgain`, red-proved). Known limit: the hub Ingress reads for 60 s, so a very slow first try can show a gateway timeout while provisioning finishes in the background (pressing again is safe). **READY** — operator ruling 2026-10-12 (S29, `09` §3 decision 224): yes — the scan prefers IPv4, waits longer, and the error says „safe to press again". **READY** — owner Viktor | — | The next real off-site setup (a new customer): read the hub log for the scan; success or the new message → close | CC |
| **R-516** | Install & onboarding | P3 | **[P3-LOW] English a customer meets on a fresh box and its apps' first screens — enumerated by the big night.** MEASURED 2026-09-14 (BIGNIGHT, VM 333, ISO 1.27.1, controller 0.242.0). Felhom-owned: (1) the dashboard menu item **„Debug"**; (2) the dashboard CPU tile **„Load: 0.29 / 0.39 / 0.37"**; (3) the launcher tile **„Filebrowser"** opens a login in English with no Felhom text (R-513); (4) the storage page mixes formal „Adjon hozzá / Csatlakoztasson" with the product's „te". App first screens a household meets before any Felhom text helps: (5) **Uptime Kuma 2.4 opens on „Which database would you like to use?"** (SQLite / Embedded MariaDB, „Next") — the app card's „Első lépések" does not mention it; (6) PrivateBin, Gokapi, AdventureLog and FileBrowser UIs are English (the apps' own). Already rows: the Proxmox installer screens (R-495, answered by the guide), `wiki.DOMAIN` (R-498). **Fix shape:** rename „Debug"/„Load" (controller); add the Uptime Kuma database step to its card, or pre-seed `db-config.json` for SQLite in the template (catalog). **Added by F4 (20:04:54Z):** (7) the storage page prints the disconnect time as a raw ISO UTC string „Leválasztva: 2026-09-14T19:58:02Z"; (8) the „Meghajtó leválasztva" banner appears twice on every page; (9) „4 telepített alkalmazás nem fut — nézze meg a rendszermonitort" uses the formal form. **Added by F7 (20:50–21:00Z, system disk at 95 %):** (10) a banner on every page in English, „**SSD disk usage high: 90%**"; (11) the dashboard tile reads „Rendszer (/) 61.8 GB / 68.7 GB (**90%**)" while `df` reports **95 %** (reserved blocks ignored), and „(/)" labels the data volume `/mnt/sys_drive`; the deploy page says nothing about free disk. **Added by the i18n spike (2026-09-17, controller v0.247.0):** (12) **six formal („ön") forms in the converted dashboard copy** — „Olvassa be telefonnal" (launcher QR hint), „Biztosan kikapcsolja a megosztást?" (launcher), „Ha újratelepíti … importálnia kell" (the layout's remove-app modal), „Kérjük, vegye fel a kapcsolatot" (backups empty state). They were NOT fixed — a localisation release may not change Hungarian bytes — and are now COUNTED by `controller/scripts/i18n_missing_gate.py` (`HU_FORMAL_CEILING` = 6, a ratchet: a new one convicts, fixing one lowers it). The inventory (`audits/I18N-INVENTORY-2026-09-17.md`) is the list this row closes against in localisation slice 6 (R-561). **Extended 2026-09-17 by slice 1 (controller v0.248.0–v0.250.0):** the converted copy now counts **16** formal forms (`HU_FORMAL_CEILING` 6 → 10 → 12 → 16, each raise stated, Hungarian unchanged by rule) — and the count is an UNDER-count: the gate's stem list sees 4 on the release C pages (storage, drive wizards, debug), while a wider list („írja be”, „adja meg”, „adjon hozzá”, „válassza ki”, „biztosan eltávolítja”, „engedélyezze”, „hozzon létre”, „kattintson”) finds at least **22** keys there alone (login „Adja meg a jelszavát”, the NAS guide, the storage confirms). Widen the stems when this row is worked; the ceiling rises with them. **NARROWED 2026-09-20 by localisation slice 6's walk, item by item** (`audits/i18n-slice6-2026-09-20/R-516-item-by-item.md`): item 2 FIXED (slice 1), items 3 and 6 APP-OWNED (and for an English household item 6 is an advantage), item 11 is a wrong NUMBER rather than a language and needs its own row, item 1 is correct English for an English reader and still open for a Hungarian one. **Items 4, 7, 8, 9 and 10 are NOT DECIDABLE FROM AN ENGLISH WALK** - they are about Hungarian copy quality, a disconnected drive and a full disk, none of which this walk had. **What this row is now waiting for is a HUNGARIAN walk on a box with a second drive**, not another English one; saying it closed would be the kind of closure that makes a register stop meaning anything. **-- 2026-10-08:** the recovery screen's own answers are Go literals in Hungarian only — 14 `renderRecovery` messages in `controller/internal/web/recovery_handlers.go` (seen: the safe default „A művelet nem fejeződött be…" rendered on an English page in a red-proof run). Today's new R-304 sentence is a bundle key in both languages; the 14 old ones are not. | **NARROWED 2026-09-20 - rank P3-LOW; owner: CC; needs a Hungarian walk** **2026-10-05 (burn-down night): items 1 and 4 FIXED on controller `main`** (`d7efa7b`): the menu says „Hibakeresés” (English „Debug”); the storage pages use the te-form (formal-form ceiling 17 → 14; 116 parity fixtures changed by exactly those bytes). Item 2 was already fixed. **Left:** items 5–6 (apps' own screens) and the rest. **2026-10-06: items 1 and 4 DELIVERED** (controller v0.298.0). **2026-10-06 (burn-down night, later): items 7–10 and 12 FIXED on controller `main`** (`e4774e6`, `2e5ef7e`): the disconnect time in local time; one banner per unpluLine truncated
@@ -192,7 +193,7 @@ stopping line that lies.
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
| **R-985** | Security & access | P3 | **Our zero-knowledge wording is stronger than the product: on a live box the operator can reach the off-site backup key (through the vaulted root credential and the operator tunnel) without the customer's recovery code.** Design-shaped; decision for the operator in `STATUS.md`. Fix options: change the wording, or remove operator root reach to the key. Security review 2026-10-12 (`audits/security-review-2026-10-12/`); detail held off-git for the operator (`/mnt/5_hdd/felhom.eu/evidence/security-review-2026-10-12-PRIVATE/`). | WAITING-ON-OPERATOR — design-proposal | operator decision | Decide the wording or the change | operator |
| **R-983** | Security & access | P3 | **Seven small dashboard weaknesses: after sign-in the browser can be sent to another site; the server-reboot route acts on a GET, so a link can reboot the guest; no anti-framing headers; parallel sign-in attempts get past the limiter; the settings page allows 8-character passwords; reset-code requests have no limit on the box; logout can be triggered from another site.** Fix each with a test, in one controller release. Security review 2026-10-12 (`audits/security-review-2026-10-12/`); detail held off-git for the operator (`/mnt/5_hdd/felhom.eu/evidence/security-review-2026-10-12-PRIVATE/`). | OPEN | — | One hardening release | CC |
| **R-983** | Security & access | P3 | **Seven small dashboard weaknesses: after sign-in the browser can be sent to another site; the server-reboot route acts on a GET, so a link can reboot the guest (**FIXED 2026-10-13 in controller v0.310.0, R-998 session: POST only, `TestServerReboot_PostOnly` — six remain**); no anti-framing headers; parallel sign-in attempts get past the limiter; the settings page allows 8-character passwords; reset-code requests have no limit on the box; logout can be triggered from another site.** Fix each with a test, in one controller release. Security review 2026-10-12 (`audits/security-review-2026-10-12/`); detail held off-git for the operator (`/mnt/5_hdd/felhom.eu/evidence/security-review-2026-10-12-PRIVATE/`). | OPEN | — | One hardening release | CC |
| **R-977** | Security & access | P3 | **FileBrowser's default password can come back if its data volume is ever re-created, because the box checks it only once; and the tunnel can start before the box is claimed.** Static; needs a live check on 9202. Fix: check at every start, and do not start the tunnel before the claim. Security review 2026-10-12 (`audits/security-review-2026-10-12/`); detail held off-git for the operator (`/mnt/5_hdd/felhom.eu/evidence/security-review-2026-10-12-PRIVATE/`). | OPEN | — | Measure on 9202; then fix | CC |
| **R-976** | Security & access | P3 | **A family member's session can be copied out of a family-gated app, and a dashboard password reset does not end family or gate sessions; so a stolen session keeps working for up to 30 days.** Fix: give each app its own derived token instead of the session itself, and end every family and gate session on a reset. Security review 2026-10-12 (`audits/security-review-2026-10-12/`); detail held off-git for the operator (`/mnt/5_hdd/felhom.eu/evidence/security-review-2026-10-12-PRIVATE/`). | OPEN | — | Fix with tests in a controller release | CC |
| **R-970** | Security & access | P2 | **Whoever holds a box's own hub API key — the hub, or anyone with a copy of the hub database — can read the box's settings file with every secret in it and replace that file through the internet; the box does not check that such a change is signed.** This contradicts `01` §4 (the box exposes no control endpoint) and §10 (a hub compromise alone cannot forge commands). Nothing in the product calls this route any more. Fix: remove the route, or require an operator-signed op for it. Security review 2026-10-12 (`audits/security-review-2026-10-12/`); detail held off-git for the operator (`/mnt/5_hdd/felhom.eu/evidence/security-review-2026-10-12-PRIVATE/`). | OPEN — confirmed by a second reader | — | Remove the route with a test that it is gone; release the controller | CC |