# REPORT — weekend night 2026-10-23: the hub's last plain tokens locked; the upload and CPU watch (spike, design, build) **Safest readings taken (unattended), stated first:** - **A test image is not a release.** The upload watch was proven on scratch 9202 with `felhom-controller:0.342.0-rc.1`, set by hand in 9202's `/etc/felhom-controller-image` (the shape of today's security-lane rc.1). An `-rc` tag is never offered to a household's update button. No floor was touched; only 9202 ran it, and 9202 is back on 0.341.0. - **9202's line was seeded by hand** (one 40 Mbit/s record): its off-site backup is off by design, so it can never learn a line itself. The learning path is pinned by unit tests and by tonight's one-second read of the real pushes on the three test boxes (read only). - **The hub 0.168.0 image was built** (the brief asked for it) **and not deployed.** The four new hub event types are in hub 0.169.0, committed only. - **Per-app trigger instead of a whole-box trigger.** The brief said „the box's upload above X % of the line". Inside its container the controller cannot tell LAN from internet bytes, nor see the box's total; it can see each app's exact bytes, and the per-app view is what the explanation compares. So the trigger is per app (§6.2 of the spike). - **The first day:** apps the box can ask are judged at once on the 5 Mbit/s floor (a mismatch is evidence whatever the line); apps it cannot ask are judged only after ~20 h of history. The brief's „first day: no alarm, only learning" holds for those apps; for the others a mismatch on day one is still an alarm. ## Five lines 1. **Tokens locked:** the Cloudflare tunnel token, the Cloudflare API token and the git token are sealed in **hub 0.168.0** — built, CI green, **not deployed** (you deploy it on Monday). 2. **Backup copies:** two options — encrypt the hub's Longhorn disk, or take it out of the Longhorn backup because the hub's own copy to ep0 is already encrypted at rest and in transit (measured). **My pick: take it out** (D40). 3. **The line:** measured from the box's own nightly off-site upload (one-second samples, best 10 seconds) — a lower bound; most nights the upload is too small to say anything, so a real test is your decision (R-1247). 4. **Apps that explain their traffic:** qBittorrent and Transmission (their own byte counters, within 1–7 %), Plex (its viewers and transcodes). Jellyfin needs a key the household makes; Nextcloud and Immich give no number. 5. **The fake hacked app was caught: yes** — a flood from inside qBittorrent, hidden behind a real seed: one alarm after 15 minutes, one clear 15 minutes after it stopped. A CPU burner in Plex: the same. A real seed: no alarm. ## The spike's numbers `documentation/audits/SPIKE-upload-watch-2026-10-23.md` (evidence beside it). Short form: - **Normal levels** (three boxes, 19–31 days of one-minute samples): no app's own upload p95 reached 6 Mbit/s; the longest run at ≥ 5 Mbit/s was **3 minutes**, at ≥ 1 Mbit/s 9 minutes. CPU: the longest run at ≥ 150 % of a core was **7 minutes**, at ≥ 80 % 13 minutes. DooPlex's hacked Plex: ~44 Mbit/s for hours, ~10 cores. - **Line speed:** at a one-minute sample the push exceeded 2 Mbit/s on 3 of 81 box-nights (the push moves a few MB); the box keeps no byte count of it. Tonight's one-second read: §Night data below. No active test was run (ep0 changes forbidden; a public test's terms not read). - **Apps:** qBittorrent counted 131.1 Mbit/s while its container sent 134.6–140.5 (×1.03–1.07); Transmission ×1.001–1.02; the limit API works and persists (KiB/s rounding); Plex answers its session lists (with the token it already has when claimed); Jellyfin 401 without a household-made key. - **A hard cap** (`tc` from a helper in the app's network namespace) works in an unprivileged guest: a flood held at 20 Mbit/s, the app kept answering. Not built (R-1248). ## The household's line (final text) - HU: „A qBittorrent feltöltése legfeljebb N Mbit/s, hogy a családi internet gyors maradjon. A qBittorrentben átállíthatod." - EN: "qBittorrent uploads at most N Mbit/s, so the family internet stays fast. You can change this in qBittorrent." - The system settings page: „Feltöltési sebesség: legalább N Mbit/s (az éjszakai távoli mentésből mérve)" / "Upload speed: at least N Mbit/s (measured from the nightly off-site backup)"; „még mérjük …" until known. ## Morning list (Monday) 1. Deploy hub 0.168.0 (the last plain tokens locked). Then read its start line `config tokens sealed at rest (8 …)`. 2. D40: the hub's plain Longhorn copies — stop that backup (my pick) or encrypt the disk; and whether to delete the two plain copies already there. 3. D41: qBittorrent's default limit — 50 % of the measured line (my pick). D42: who is told — the family only about the limit, the hacked-app alarms only you (my pick). 4. Release the upload watch when you say yes (hub 0.169.0 build + deploy, then controller 0.342.0 through the normal path). 5. For the homelab session (list only, no HM numbers): an upload-saturation alert on DooPlex's `eno1` (the line is ~100 Mbit/s; the flood peaked at 905 Mbit/s). ## Commits, images, CI (by commit) | Repo | Commit | What | CI | |---|---|---|---| | felhom.eu | `5277d859` | hub 0.168.0: the tokens sealed; R-1243; STATUS D40 | run 2269 **success** | | felhom.eu | `af5c005a` | hub 0.169.0: the four events (allowed, operator-only, mail line) | run 2271 **success** | | felhom.eu | `a91d0b24` | spike, design (`08` §9), live proofs, rows R-1244..R-1248, STATUS | run 2272 **success** | | felhom-controller | `a9c1997f` | v0.342.0 (unreleased): the upload and CPU watch | run 2270 **success** | | felhom-controller | `60e487d` | CONTEXT + REPORT | run 2273 **success** | Images: `felhom-hub:0.168.0` `sha256:3bfdecaae527d4762059600cdfaad83daf4e4b5b20661fc47acf128180d87cb3` (from `5277d859`, not deployed); `felhom-controller:0.342.0-rc.1` (from `a9c1997f`, scratch 9202 only, removed from 9202 after). **Teardown, three layers:** machine — 9202: the four throwaway apps removed through the product (containers and volumes gone), my test files, probes and the `alpine:3.20` helper image removed, back on 0.341.0, the seeded record removed; host — demo-hp: the receiver LXC 9471 destroyed with its disk; DooPlex: my private copy of the hub snapshot shredded, the 9202 password copy shredded; hub — none. ep0: read only (the hub-copy snapshot list). No household box touched. ## Rows **Before 188, after 194; opened 6 (R-1243 … R-1248), closed 0.** R-975 stays open (WAITING-ON-OPERATOR: deploy 0.168.0). ## Observations - The hub's Cloudflare/tunnel/git tokens unsealed in `config_json` — FIXED in hub 0.168.0 (R-975, not yet deployed). - The hub's Longhorn copies are plain and sit in the open folder — FILED: R-1243. - The upload watch is not released — FILED: R-1244; its limit size — FILED: R-1245; who is told — FILED: R-1246. - The line is only a lower bound — FILED: R-1247. An app's API can lie — FILED: R-1248. - `appliance_registrations.pairing_code` is plain — NOT-A-FINDING: it is shown on the box's screen and is one of two factors; the other is hashed. - R-879's sealing (0.138.0) ran no VACUUM, so old plaintext of those columns could sit in freed blocks of the live `hub.db` — NOT-A-FINDING: 0.168.0's one-time VACUUM rewrites the file on its first start. - The hub CHANGELOG's 0.167.0 header still said „NOT built" though it is deployed — fixed without a row (small). - The shared memory index went over its size ceiling twice in one day (another session's lines) and blocked the push gates — fixed without a row (shortened, no link lost); it sits 14 bytes under the ceiling now. - The watch's very first minute after a start cannot reach apps by name (network joined seconds later) — NOT-A-FINDING: that minute only sets baselines. - The media wiring's generated override file stays after an app's removal — NOT-A-FINDING: rewritten before every start. - `docker stats` text in the stored metrics has three significant digits — NOT-A-FINDING for the watch (it reads the Engine API); the charts are display only. - `felhom-samba` runs on the host network and has no counter — NOT-A-FINDING: infrastructure, not judged. - Transmission's rate under its limit was not observed (no peer contact) — NOT-A-FINDING: the limit API was read back; qBittorrent's was observed. - A dated check came due on the push (R-1214, Tester 1's night copy): read read-only — the guest's memory „full" stall was 444 µs in total over 11 h, 0 OOM kills; the copy was small (0.24 GB) and the capped container is not shown — the row stays WATCHING, the due line removed. - The night's line floor is low (10 Mbit/s on demo-hp, one second at 70) — FILED in R-1245's text (D41), no new row. ## Instruction files, poster No instruction file edited. System poster: no fact on it changed (nothing deployed) — none of the three applied. ## Night data Read 02:08–02:30Z, once a second, on the three test boxes (read only; spike §2.1): - **demo-hp: counts.** Best 10 seconds 12.6 MB = **10.0 Mbit/s**. One second reached 70 Mbit/s. - **demo-felhom: too small** (best 10 seconds 3.2 MB = 2.5 Mbit/s). - **Tester 1: too small** (3.5 MB = 2.7 Mbit/s). **What it means:** the nightly push is a burst of a few seconds. So the line it gives is a low floor. On demo-hp, 50 % of that floor is about 5 Mbit/s for qBittorrent — probably much less than the real line. The floor rises only when a bigger night comes (the box keeps the best of 14 nights). Added to the D41 decision row (R-1245): if you want a looser limit before a real line test exists (R-1247), pick a higher share or „none".