https only (R-1033, P1): controller v0.312.0 proven on Tester 1, demo-hp, demo-felhom (29 names); scripts/https_only_check.py; 01/13/poster facts; decision 246 (decided by CC unattended); R-1033 P1 narrowed; R-1041..R-1043
gates / gates (push) Successful in 6m34s

This commit is contained in:
2026-10-14 22:49:54 +02:00
parent 0144be8cdd
commit b7807c1198
17 changed files with 297 additions and 4 deletions
@@ -139,6 +139,19 @@ read the LEFTMOST entry have the chain removed on their router. Design and measu
MeTube) was SPIKED on this base and passed (`audits/permanent-gate-2026-10-01/VERDICT.md`) and is BUILT since
controller 0.287.0 (`09` §3 decision 64) — the family gate, at the end of this section.
**Every public name answers only over https (recorded 2026-10-14, R-1033 — P1 by the operator, `09` §3 decision 244;
controller ≥ 0.312.0).** A visitor who types `http://` reaches Cloudflare over plain http; the tunnel still delivers to
traefik's :443 and says so in `X-Forwarded-Proto: http` (measured), which traefik keeps only from the tunnel's address.
The first link of the entrypoint chain `felhom-forwarded` (`felhom-https-only`) answers it with a 301 to the same https
URL, path and query kept, BEFORE any app sees the request — so a login page is never served, and a password never sent,
over http. A name with no app route is redirected by a priority-1 catch-all that matches only `X-Forwarded-Proto: http`.
HSTS: one day, no subdomains, no preload (a longer value is the operator's decision). **Where it is enforced:** on the box
(`dynamic/forwarded.yml`, rewritten on every health tick if it drifts), not at Cloudflare: the boxes' Cloudflare keys
cannot change zone settings (403, measured on every zone) — Cloudflare's „Always Use HTTPS" is the operator's optional
second layer. **How it is checked:** `scripts/https_only_check.py` lists every public name from the box's own traefik
routes and demands 301/308 to the same https URL (negative control: a stub answering plain 200 must be convicted).
Evidence: `audits/https-only-2026-10-14/`.
**Who may reach an app, and through what (recorded 2026-09-29 — no document said it before; spike finding F1).**
Every app is reached only through the box's traefik (no catalog app publishes a host port except crafty-controller's
game ports; none uses host networking — read from the catalog 2026-09-29). traefik routes by host name: the tunnel's
@@ -922,6 +922,15 @@ its length, and both fixes cost something the household would notice — operato
245. **Drive events = option A.** On a drive added, removed or back, the box restarts the media apps; it makes a new
drive ready inside each media app; it pauses torrents before a safe removal; and it adds guards only where a
measurement shows loss. *Operator ruling 2026-10-14 22:13.* (R-1023; `15-media-library.md` §10)
246. **Where https-only is enforced: on the box (traefik), not at Cloudflare.** *Decided by CC unattended 2026-10-14
22:40 — operator may reverse.* Question: which switch makes every public name answer only over https tonight?
Options: (a) Cloudflare „Always Use HTTPS" + HSTS per zone — one switch per zone, nothing on the box can forget it,
but every box's `cf_api_token` lacks „Zone Settings" (403 on all three zones, measured) and no account key is
stored: costs a key change per customer first; (b) traefik on the box redirects when the trusted tunnel says
`X-Forwarded-Proto: http` — needs a controller release (v0.312.0), reaches a box with the floor, and is rewritten
on drift every health tick; (c) a Cloudflare firewall rule (the keys CAN edit those) — it can only BLOCK, which
breaks Sonarr's login (its own `http://` redirect). Chosen (b): it works with the keys we have, was measured
(bench + three zones), and leaves (a) open as a second layer once the operator widens the keys (R-1042).
### 2026-10-14 (evening, ~21:20) — the media folders and the N100 (recorded before the work; the media-folder-fix brief)
@@ -58,6 +58,7 @@ OS packages (exact versions, apt-signed), the installer script (by tag, R-980).
| boundary | verdict | evidence |
|---|---|---|
| end-user ↔ apps | holds, with gaps | traefik routing and header stripping hold; homepage has the Docker socket and no gate (R-981) |
| **added 2026-10-14 (R-1033, P1):** a visitor ↔ Cloudflare over plain http | holds on boxes with controller ≥ 0.312.0 | before: every public name answered `http://` (login pages too), and Sonarr/Radarr/Prowlarr redirected https visitors to an absolute `http://` login; a password could cross an untrusted network unencrypted (attacker: anyone on the visitor's network). Now traefik sends every `http://` request to https (301) before an app sees it, HSTS one day; boxes below 0.312.0 (Tester 2 at 0.292.0) keep the gap until the floor delivers it. `audits/https-only-2026-10-14/` |
| 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), 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) |
| **added (R-1007):** controller → an app's own job queue | holds — nothing new | the `after_unclean_boot` commands run inside the app's container through the docker socket the controller already holds, and read the app's database with a read-only session: no key into the household's account, nothing a compromised controller could not already do (exec in any app, read its database). The household's lost-file names stay on the box (`app.yaml`, the app page); the operator event carries counts only, and a failing command's output is never logged (security review of controller `8d862e4`, fixed in `3b85a0c`) |
@@ -69,7 +69,7 @@ Inside, from bottom to top:
Note on the drawing: the data drives are attached to the guest from the host; the **whole-guest backups do not include the data drives** (that is what the per-app tiers are for).
Arrows out of the box:
- **Household's phone / laptop → Cloudflare → cloudflared → Traefik → app** (solid, green). Label: "Remote access. HTTPS ends at Cloudflare's edge, so Cloudflare could technically see this traffic (later item: our own relay)." Also a short local arrow: "At home on the LAN: direct to Traefik."
- **Household's phone / laptop → Cloudflare → cloudflared → Traefik → app** (solid, green). Label: "Remote access. HTTPS ends at Cloudflare's edge, so Cloudflare could technically see this traffic (later item: our own relay)." **Since 2026-10-14 (R-1033): https only** — a visitor who types `http://` is sent to `https://` by Traefik before any app answers (HSTS one day). Also a short local arrow: "At home on the LAN: direct to Traefik."
- **Box → hub** (dotted): "Reports every few minutes; asks for signed jobs. The hub never connects into a box."
- **Box → ep0** (dashed, through a small padlock tunnel labelled **WireGuard**): "Whole-guest backup, encrypted on the box, sent through WireGuard." — *fix (b), 2026-10-09: the label must name BOTH, because "encrypted on the box" alone read as if WireGuard were doing the encrypting.*
- **Box → Hetzner Storage Box** (dashed): "Per-app off-site backup (restic), encrypted on the box. The box's key can only ADD copies; old copies are removed only in a weekly clean-up window the hub opens."