diff --git a/CONTEXT.md b/CONTEXT.md index 2f856e28..203ce83d 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -16,6 +16,17 @@ > and holds nothing of its own; this file does hold its own content, namely the standing rulings below. +> **2026-10-23 ~12:00 — operator rulings by starting the D39 brief (`09` §3 decision 255; R-1239 closed).** **D39 = B:** a +> `security_fix` ladder step — box walk on 9202 required, bench optional, taken by the night leg. **No operator word per +> incident:** a public advisory id (CVE or GHSA) + its https:// notice is the authority; the session that writes the step +> reports it to the operator after the push. The design (Claude.ai's recommendation) was taken as written; one fact in the +> brief was checked and held (Plex's box fixture exists but is a `NoRoute` stub, so `seed: "none"` is right). Built: +> catalog `3898593` (`ladder.py` `check_security` + `engine_majors_crossed` importing `check-engine-major.py`; the move gate +> accepts it; `check-test-record.py` WARNs on a head with no bench; `upgrade-test.py --write-ladder --security`; +> `box_walk.py --security` + `refresh_box_mirror()`), controller **0.341.0** (`legCandidate` takes `proven` or +> `security_fix`; global floor 11:24 UTC with MinAgent 0.158.1, the operator's yes in chat). Proof: +> `audits/security-lane-2026-10-23/`. Older controllers skip the step at night and take it on a hand press (proven). + > **2026-10-23 10:56 — operator ruling: Plex to the fixed version now, without the test record (Plex2Shell).** > Verbatim: *„bump the version to the newest in the app template immediately (as there are no customers yet), I want the > demo boxes on the newest version asap."* It skips `09` §3 decision 13's test record **once, for Plex only**. Done: catalog diff --git a/REPORT-security-lane-2026-10-23.md b/REPORT-security-lane-2026-10-23.md new file mode 100644 index 00000000..c58b1240 --- /dev/null +++ b/REPORT-security-lane-2026-10-23.md @@ -0,0 +1,17 @@ +# REPORT — D39 = B: the security-fix lane (2026-10-23, attended) + +**Plain words first.** An app with a public security hole can now get its fix at night on every box, after one test +on the test box. Controller 0.341.0 does it and is the floor since 13:24 (your yes). Older controllers skip such a fix +at night and take it when a person presses Update — both seen on the test box. Nothing needs you. + +- Design: `09-update-architecture.md` §3 decision 255 (+ a dated note on decision 13). Capability map B23. +- Catalog `3898593` (+ a box-walk address fix after it); controller `63dcccf` = 0.341.0; drill commits `8c1d908` … + `17e0da0` (the drill only). +- Evidence and the full table: `documentation/audits/security-lane-2026-10-23/README.md`. +- Register 186 → 186: closed R-1239 (D39 = B); opened R-1240 (9202's 34 saved configs with the catalog token); R-1238 + stays open, its text now says the next Plex fix can use the lane. +- `STATUS.md`: D39 out of the decide table, one „what works" line, the „Updated" paragraph replaced (the old one is in + `STATUS-HISTORY.md`). `CONTEXT.md`: the rulings. System poster: no fact changed (none of the three actions). +- Instruction / fact files edited: `app-catalog-felhom.eu/CLAUDE.md` (the lane, added); `operations/nodes.md` line 221 + (9202 „same dashboard password" → its own password, measured) and line 46 (Tester 1 `.101` → DHCP, `.148` today). +- No `--no-verify` in this run. diff --git a/STATUS.md b/STATUS.md index 694aeb60..70a6cfff 100644 --- a/STATUS.md +++ b/STATUS.md @@ -2,7 +2,7 @@ **Ready for Tester 2? Yes — tell the friend go (hand-over list: `documentation/pilot/TESTER2-handover.md`).** -**Updated 2026-10-23 (day).** Released on your go: controller 0.340.0 (the floor), agent 0.163.0 (vouched; root SSH by key only on the three test boxes), installer 1.36.0, hub 0.167.0, and the website with your two sentences. The hub database now leaves every 6 hours and the released images and agent packages every night, both read back from ep0; the break-glass sheet is current — please print it. One false failure mail reached you at 10:28 (my install of the 6-hour copy, fixed in the same hour). +**Updated 2026-10-23 (afternoon).** Your D39 = B is built and live: an app with a public security hole can now get its fix at night on every box, after one test on the test box — controller 0.341.0 is the floor since 13:24 (your yes). The test: Plex moved by itself at night on the test box, and an older controller safely waited for a hand press. Nothing needs you. ## What works - A box installs from nothing with the public installer, links to the hub, and moves to the floor by itself (map A1, A3, A4). @@ -12,6 +12,7 @@ - After a power cut the box starts by itself, cleans up a half-written backup, and Immich repairs broken photos (map C10, F12). - A second drive is offered as the backup target; media apps see every drive; a one-disk box gets its own data area (map D1, D13, D15). - The three test boxes answer only over https; the hub's key opens nothing on a box; password guesses are counted (map E12, E13, E17). +- An urgent security fix reaches the boxes at night after one test on the test box; the box backs up first and undoes a failed update (map B23). - The hub mails alarms and recoveries, and keeps one whole-box copy per week on ep0; CC uses its own limited token, not your password (map F1, F11, G15). ## What is broken @@ -29,7 +30,6 @@ Newest first. CC's pick is in the „Pick" column. Each row also has its full te | # | Row | Question | Pick (why) | The other option costs | If you do nothing | Blocked | |---|---|---|---|---|---|---| -| D39 | R-1239 | When an app has an urgent security hole, may its fix reach the boxes at night without the full update test? Today it needs your word and a hand press on each box (Plex, today). | **Yes — a marked „security fix” step** (tested on one test box only; the night update then takes it; works without you awake) | stay as today: your word and a CC session for every hole; households wait for the press | the same as today | — | | D38 | R-1235 | Put the newest golden (637 MB) into the nightly release copy on ep0 too? | **Yes** (a new install after a DooPlex loss needs it; it fits under the 3 GB cap) | leave it out: after a loss, no new install until a golden is rebuilt and vouched | the same | — | | D37 | R-1236 | Raise Gitea's memory limit from 1 GiB to 2 GiB? It was killed for memory 4 times in 17 days, once during today's release. | **Yes** (DooPlex has the RAM; a DooPlex change, so yours) | keep 1 GiB: a restart in release hours fails box updates and CI for a minute | the same | — | | D9 | R-1062 | Split the N100's USB drive in two, for multi-drive tests? | **No — keep one drive** (the lived-in box keeps its nightly whole-box backup) | Split: the box loses its local whole-box backup, or you buy a second drive first | it stays one drive; multi-drive tests stay on Tester 1 | — | diff --git a/documentation/architecture/00-capability-map.md b/documentation/architecture/00-capability-map.md index 05d13d7c..63c8b8ce 100644 --- a/documentation/architecture/00-capability-map.md +++ b/documentation/architecture/00-capability-map.md @@ -124,6 +124,7 @@ | [B20](capability-map-detail/B.md#b20) | App crashes → customer notified (one event per transition, no flapping spam) | controller v0.120, hub v0.48 | **IMPLEMENTED** | crash→customer mail never live-confirmed | `audits/CAMPAIGN-3-2026-07-11.md` (2026-07-11) | | [B21](capability-map-detail/B.md#b21) | Post-deploy optional config (API keys etc.) with restart | controller, catalog `.felhom.yml` | **IMPLEMENTED** | config-save + restart in no campaign | `tests/CAMPAIGN-2-2026-07-07.md` (2026-07-07) | | [B22](capability-map-detail/B.md#b22) | Backup classification: 13 bind-bearing apps carry mandatory/optional/excluded classes | catalog, controller v0.132–133 | **PROVEN-LIVE** | — | `audits/SPIKE-backup-classification-2026-07-14.md` (2026-07-14); `audits/CAMPAIGN-6D-2026-07-15.md` (2026-07-15) | +| [B23](capability-map-detail/B.md#b23) | An urgent security fix reaches the boxes at night — a `security_fix` ladder step (box walk required, bench optional, a CVE/GHSA notice), taken by the night update (`09` §3 decision 255, D39 = B) | catalog `3898593`, controller v0.341.0 | **PROVEN-LIVE** | scratch 9202 (drill catalog), Plex 1.43.4 → 1.43.5; an app with no box fixture is checked by health, version and a soak only (`seed: none`) | `audits/security-lane-2026-10-23/` (2026-10-23) | ## C. Protection & recovery (the product promise) diff --git a/documentation/architecture/09-update-architecture.md b/documentation/architecture/09-update-architecture.md index cdf6a095..5ef51c2e 100644 --- a/documentation/architecture/09-update-architecture.md +++ b/documentation/architecture/09-update-architecture.md @@ -286,6 +286,9 @@ builds them (`audits/update-rulings-2026-09-23/`). update night measured the proxy wrong in both directions — minor moves that held (adventurelog, outline) and a tag shape it cannot read at all (§3b Q3). The test record is the thing the proxy was guessing at. + **Dated note 2026-10-23 (decision 255, D39 = B):** a second step kind now exists — the `security_fix` step, proven + on the box with the bench optional, for an urgent upstream fix with a public notice. Decision 13's rule is + unchanged for every other move: a step a box takes was still tested, on at least the box venue. 14. **The update ladder** (Q3). A box more than one step behind **climbs one tested step at a time, in order, and never jumps**. Each step is the full guarded update of §6.1. **Why:** a step was tested @@ -1031,6 +1034,52 @@ its length, and both fixes cost something the household would notice — operato Built in controller v0.314.0 (`internal/stacks/restore_gate.go`); pinned by `restore_gate_test.go` and `r1057_unit_gate_test.go` (ten mutations each seen failing). Proof: `audits/gate-restore-2026-10-15/9202/`. +### 2026-10-23 (~12:00) — D39 = B: the security-fix step (operator ruling by starting the run; R-1239) + +255. **An urgent upstream security fix reaches the boxes at night as a `security_fix` ladder step: the box walk is + required, the bench is optional, and a public notice is the authority.** *Operator ruling 2026-10-23 ~12:00 (D39 = + B, R-1239), taken by starting the D39 brief; its design is Claude.ai's recommendation, taken with it.* Why: on + 2026-10-23 the only route for the Plex2Shell fix was an operator word, `git push --no-verify` past the move gate, + and a hand press on each box — and the night leg never takes a step with no test record (R-1238). That does not + scale past the first customers and needs the operator awake. + 1. **The entry** (`scripts/ladder.py` `check_security`, the one reader): `"verdict": "security_fix"` with `security` + `{ids: [CVE-…|GHSA-…], advisory: "https://…", fixed_in}`, `box_evidence` (required), `seed` `read_back`|`none`, + `memory_peak_pct` from the BOX soak with `memory_basis: "box_soak"`; `evidence` = the bench record or null — a + bench given must be `proven` (`bench_verdict`, `harness_version` ≥ 2 ride with it). Never `backfilled`, + `digest_from` (a same-tag fix stays decision 52's lane), `engine_conversion`, nor a database engine MAJOR (any + engine — MariaDB included; the judgement is `check-engine-major.py`'s own `engine_of`/`major_of`, imported). + 2. **No operator word per incident.** The session that writes the step reports it to the operator after the push. + 3. **The gates:** the move gate accepts a `security_fix` move exactly as a proven one (from/to per service, digests + the registry still serves); `check-test-record.py` WARNs on every push while a template's head is a security-fix + step with no bench test („: security-fix step has no bench test (since )"). Seven decoys + (`test_gate_decoys.py`), each seen failing with the rules switched off. + 4. **The box venue:** `box_walk.py --security` on 9202 through the product's Update; verdict `proven` only if the + update ended `done` with no undo and no hold, the app runs, the four version observables are TO, the seed (if + any) reads back, and a soak (default 600 s, cgroup `anon` against the limit, OOM kills and restarts counted) + sees no kill and no restart. A negative control (a TO image that exits at once) comes back `failed`. + 5. **The writer:** `upgrade-test.py --write-ladder --security …`, refusing a box walk not proven, a bench given and + not proven, a template not at the walk's FROM, a digest that moved since the walk, an engine major, a missing + field. The compose move, `catalog_since` and the superseded step's own files are the shared `apply_entry`. + 6. **The controller (v0.341.0):** the leg takes `proven` OR `security_fix`; every other guard runs unchanged, in + the same order; it logs `[update-leg] : security fix taken ( → )` and the hub report's + step carries the ids. A person's press and the badge are unchanged. **A controller ≤ 0.340.0 skips the step at + night (`not_proven`) and a hand press takes it** — proven on 9202, below. + 7. **The trade, stated.** A security-fix step skipped the bench. For an app with a box fixture the data is still + read back after the update; for an app without one (Plex today) the step is checked only by „it starts, it is + healthy, it runs the new version, and a 10-minute soak sees no kill or restart". `files_may_change` is a bench + measurement: with no bench the step carries `false`. That is accepted because the leg's own guard is not the only + net — the update takes a per-app backup first, the undo puts the pre-update data back on a failed health check + (decision 15), and a security fix that rewrites files without breaking the health check is the case the bench + would catch and this lane accepts the risk of. A session that knows a fix migrates files runs the bench. + **Proof (9202, drill catalog, 2026-10-23):** `audits/security-lane-2026-10-23/` — the negative control (TO + `busybox:1.36.1`) came back `failed` (the box undid it after 335 s) and the writer refused it; Plex 1.43.4.10903 → + 1.43.5.11029 `proven` (done in 36.9 s, the four observables at TO, digest `sha256:01bdf0ad…5c5f`, soak 600 s: anon + peak 10.7 % of 2 GiB, 0 kills, 0 restarts, `seed: none`); the writer wrote the drill step (CVE-2026-96652, -96654, + -96655, -96656; Plex's own notice as CISA KEV cites it, added 2026-10-20; fixed in 1.43.3.10861); on 0.340.0 the + night leg logged `plex: skipped — not_proven` and a hand press took the step; on 0.341.0-rc.1 the night leg logged + `plex: security fix CVE-2026-96652,CVE-2026-96654,CVE-2026-96655,CVE-2026-96656 taken` and ended `done` in 30.1 s + after the update's second-drive copy and undo copy. Released as controller 0.341.0, global floor 11:24 UTC. + ### 2026-10-15 (07:48–08:20) — bake first; Plex through its own key; HSTS one day (recorded before the media build) 247. ~~**Bake first.** A controller version is RELEASED only when its golden is baked.~~ **REVERSED by decision 250, diff --git a/documentation/architecture/capability-map-detail/B.md b/documentation/architecture/capability-map-detail/B.md index e3c2944b..bdef2ed3 100644 --- a/documentation/architecture/capability-map-detail/B.md +++ b/documentation/architecture/capability-map-detail/B.md @@ -379,3 +379,28 @@ **Gap / roadmap (as written):** Remaining ~39 apps are legacy-class by design (unit-only offsite) --- + +## B23 — An urgent security fix reaches the boxes at night (the security-fix step) + +*Map row: [B23](../00-capability-map.md#b23) · added 2026-10-23.* + +**Scenario:** an app has a published hole (a CVE or GHSA id with its notice) and a fixed upstream version. CC walks the +step on scratch 9202 through the product's Update (`box_walk.py --security`: install at FROM, the guarded Update, the +four version observables at TO, a 600 s memory soak), and the only writer turns a proven walk into a `security_fix` +ladder step. Every box on controller >= 0.341.0 takes it at night with the usual guards (backup first, undo on a +failed health check, held / stopped / needs_person / W+5h respected). + +**Components:** catalog `3898593` (`ladder.py check_security`, the two test-record gates, `upgrade-test.py --write-ladder +--security`, `box_walk.py --security`); controller v0.341.0 (`legCandidate`). + +**Status:** **PROVEN-LIVE** on 9202 with the drill catalog, 2026-10-23. + +**Evidence:** `audits/security-lane-2026-10-23/` — the negative control (TO `busybox:1.36.1`) `failed` and the writer +refused it; Plex 1.43.4.10903 → 1.43.5.11029 `proven`; on 0.340.0 the night leg logged `plex: skipped — not_proven` and +a hand press took the step; on 0.341.0-rc.1 the night leg logged `plex: security fix CVE-2026-96652,… taken` and ended +`done` after a second-drive copy and an undo copy. + +**Gap:** an app with no box fixture (Plex today, R-1238) is checked only by health, version and the soak — no data is +read back. `files_may_change` is a bench measurement and reads `false` when no bench ran (decision 255 item 7). + +--- diff --git a/documentation/audits/security-lane-2026-10-23/P2-negative-try1-install-refused/box-verdict-plex.json b/documentation/audits/security-lane-2026-10-23/P2-negative-try1-install-refused/box-verdict-plex.json new file mode 100644 index 00000000..49e736bf --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/P2-negative-try1-install-refused/box-verdict-plex.json @@ -0,0 +1,19 @@ +{ + "app": "plex", + "walk": "security", + "box_walk_version": 1, + "verdict": "inconclusive", + "venue": "box 9202 (drill catalog), the guarded Update", + "from": { + "plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c" + }, + "to": { + "plex": "busybox:1.36.1" + }, + "why": "the install at FROM did not complete — nothing was tested", + "log": [ + "12:26:15 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/plex']", + "12:26:15 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['PLEX_CLAIM', 'HDD_PATH']", + "12:26:15 [1] deploy -> 409 {'ok': False, 'error': 'Az alkalmazás adatainak helye egy meghajtó lehet, nem egy mappa a meghajtón belül. Válaszd ezt: " + ] +} \ No newline at end of file diff --git a/documentation/audits/security-lane-2026-10-23/P2-negative-try1-install-refused/walk.txt b/documentation/audits/security-lane-2026-10-23/P2-negative-try1-install-refused/walk.txt new file mode 100644 index 00000000..2e574ff4 --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/P2-negative-try1-install-refused/walk.txt @@ -0,0 +1,5 @@ +12:26:15 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/plex'] +12:26:15 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['PLEX_CLAIM', 'HDD_PATH'] +12:26:15 [1] deploy -> 409 {'ok': False, 'error': 'Az alkalmazás adatainak helye egy meghajtó lehet, nem egy mappa a meghajtón belül. Válaszd ezt: +12:26:15 [9] verdict inconclusive -> /mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/security-lane-2026-10-23/P2-negative/verdict.json +12:26:15 RESULT inconclusive — the install at FROM did not complete — nothing was tested diff --git a/documentation/audits/security-lane-2026-10-23/P2-negative/box-verdict-plex.json b/documentation/audits/security-lane-2026-10-23/P2-negative/box-verdict-plex.json new file mode 100644 index 00000000..1699be1c --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/P2-negative/box-verdict-plex.json @@ -0,0 +1,188 @@ +{ + "app": "plex", + "walk": "security", + "box_walk_version": 1, + "verdict": "failed", + "venue": "box 9202 (drill catalog), the guarded Update", + "from": { + "plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c" + }, + "to": { + "plex": "busybox:1.36.1" + }, + "observables_before": { + "pinned_images": { + "plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c" + }, + "installed_images": { + "plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c" + }, + "catalog_images": null, + "live_compose_image_lines": [ + "image: plexinc/pms-docker:1.43.4.10903-e5521bd8c" + ], + "docker_inspect": [ + "plex plexinc/pms-docker:1.43.4.10903-e5521bd8c running=true restarts=0" + ] + }, + "setup_gate_before": { + "browser": { + "status": "302", + "redirect": "https://felhom.enkisfelhom.hu/__gate/start?…" + }, + "script": { + "status": "401", + "redirect": null + } + }, + "seed": "none", + "seed_why": "the first-run claim needs a token minted at plex.tv by a real Plex account; no account exists for this venue", + "drill_commit": "577947198bff", + "sync_waited_s": 4.5, + "badge_before": { + "hu": [ + { + "title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.", + "text": "Frissítés elérhető — ma" + } + ], + "en": [ + { + "title": "A newer version of this app is available. Select the Update button to start it.", + "text": "Update available — today" + } + ] + }, + "measured_at": "2026-10-23T10:27:24Z", + "update": { + "accepted": true, + "http": "202", + "phases": [ + { + "t": 0.0, + "phase": "backing-up", + "label": "Biztonsági mentés készül a frissítés előtt…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 11.3, + "phase": "safety-dump", + "label": "Adatbázis pillanatkép…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 12.3, + "phase": "pulling", + "label": "Új verzió letöltése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 14.4, + "phase": "copying", + "label": "Az adatok másolása a frissítés előtt…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 24.7, + "phase": "starting", + "label": "Indítás az új verzióval…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 25.7, + "phase": "verifying", + "label": "Működés ellenőrzése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 327.2, + "phase": "undoing", + "label": "Visszaállítás az előző változatra…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 335.4, + "phase": "undone", + "label": "Visszaállítva az előző változatra", + "updating": false, + "error": null, + "hold": null + } + ], + "duration_s": 335.4, + "final_phase": "undone", + "update_error": null, + "hold_reason": null, + "state": "running" + }, + "observables_after": { + "pinned_images": { + "plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c" + }, + "installed_images": { + "plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c" + }, + "catalog_images": { + "plex": "busybox:1.36.1" + }, + "live_compose_image_lines": [ + "image: plexinc/pms-docker:1.43.4.10903-e5521bd8c" + ], + "docker_inspect": [ + "plex plexinc/pms-docker:1.43.4.10903-e5521bd8c running=true restarts=0" + ] + }, + "badge_after": { + "hu": [ + { + "title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.", + "text": "Frissítés elérhető — ma" + } + ], + "en": [ + { + "title": "A newer version of this app is available. Select the Update button to start it.", + "text": "Update available — today" + } + ] + }, + "state_after": "running", + "hold_after": null, + "to_digests": { + "plex": "sha256:e0ab27395614a8e1a4fdf84c6bc60ac664915cfdde70c52d030c7728a1c48e14" + }, + "why": "the update ended 'undone' (error None, hold None)", + "log": [ + "12:26:46 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH', 'PLEX_CLAIM']", + "12:26:46 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}", + "12:27:06 [1] deployed, controller state=running, pinned={'plex': 'plexinc/pms-docker:1.43.4.10903-e5521bd8c'}", + "12:27:06 gate: plex is gated — passed as the household (cookie set)", + "12:27:10 [2] setup gate as installed: {'browser': {'status': '302', 'redirect': 'https://felhom.enkisfelhom.hu/__gate/start?…'}, 'script': {'status': '401', 'redirect': None}}", + "12:27:10 [3] seed: none — the first-run claim needs a token minted at plex.tv by a real Plex account; no account exists for this venue", + "12:27:11 [5] drill commit 577947198bff: plex plexinc/pms-docker:1.43.4.10903-e5521bd8c -> busybox:1.36.1 (push rc=0)", + "12:27:19 [5] the box's drill mirror now at 57794719", + "12:27:24 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'}", + "12:27:24 + 0.0s phase=backing-up label=Biztonsági mentés készül a frissítés előtt… err=None hold=None", + "12:27:35 + 11.3s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None", + "12:27:37 + 12.3s phase=pulling label=Új verzió letöltése… err=None hold=None", + "12:27:39 + 14.4s phase=copying label=Az adatok másolása a frissítés előtt… err=None hold=None", + "12:27:49 + 24.7s phase=starting label=Indítás az új verzióval… err=None hold=None", + "12:27:50 + 25.7s phase=verifying label=Működés ellenőrzése… err=None hold=None", + "12:32:51 + 327.2s phase=undoing label=Visszaállítás az előző változatra… err=None hold=None", + "12:33:00 + 335.4s phase=undone label=Visszaállítva az előző változatra err=None hold=None" + ] +} \ No newline at end of file diff --git a/documentation/audits/security-lane-2026-10-23/P2-negative/walk.txt b/documentation/audits/security-lane-2026-10-23/P2-negative/walk.txt new file mode 100644 index 00000000..f80eaa6b --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/P2-negative/walk.txt @@ -0,0 +1,22 @@ +12:26:46 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH', 'PLEX_CLAIM'] +12:26:46 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +12:27:06 [1] deployed, controller state=running, pinned={'plex': 'plexinc/pms-docker:1.43.4.10903-e5521bd8c'} +12:27:06 gate: plex is gated — passed as the household (cookie set) +12:27:10 [2] setup gate as installed: {'browser': {'status': '302', 'redirect': 'https://felhom.enkisfelhom.hu/__gate/start?…'}, 'script': {'status': '401', 'redirect': None}} +12:27:10 [3] seed: none — the first-run claim needs a token minted at plex.tv by a real Plex account; no account exists for this venue +12:27:11 [5] drill commit 577947198bff: plex plexinc/pms-docker:1.43.4.10903-e5521bd8c -> busybox:1.36.1 (push rc=0) +12:27:19 [5] the box's drill mirror now at 57794719 +12:27:24 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} +12:27:24 + 0.0s phase=backing-up label=Biztonsági mentés készül a frissítés előtt… err=None hold=None +12:27:35 + 11.3s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None +12:27:37 + 12.3s phase=pulling label=Új verzió letöltése… err=None hold=None +12:27:39 + 14.4s phase=copying label=Az adatok másolása a frissítés előtt… err=None hold=None +12:27:49 + 24.7s phase=starting label=Indítás az új verzióval… err=None hold=None +12:27:50 + 25.7s phase=verifying label=Működés ellenőrzése… err=None hold=None +12:32:51 + 327.2s phase=undoing label=Visszaállítás az előző változatra… err=None hold=None +12:33:00 + 335.4s phase=undone label=Visszaállítva az előző változatra err=None hold=None +12:33:18 [9] verdict failed -> /mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/security-lane-2026-10-23/P2-negative/verdict.json +12:33:18 RESULT failed — the update ended 'undone' (error None, hold None) +12:33:27 [X] stop -> 200 {'ok': True, 'message': 'Stack plex stop completed'} +12:33:59 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'plex', 'volumes_removed': ['plex_plex_config', 'plex_plex_transcode'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'us +12:34:08 [X] after remove: deployed=False leftovers='/opt/docker/stacks/plex' diff --git a/documentation/audits/security-lane-2026-10-23/P2-negative/writer-refusal.txt b/documentation/audits/security-lane-2026-10-23/P2-negative/writer-refusal.txt new file mode 100644 index 00000000..2146d92b --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/P2-negative/writer-refusal.txt @@ -0,0 +1 @@ +REFUSED plex: the box walk is 'security' with verdict 'failed' — only a PROVEN `box_walk.py --security` walk is written as a security-fix step diff --git a/documentation/audits/security-lane-2026-10-23/P2-oldctl/1-install-at-1.43.4.txt b/documentation/audits/security-lane-2026-10-23/P2-oldctl/1-install-at-1.43.4.txt new file mode 100644 index 00000000..c3d15050 --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/P2-oldctl/1-install-at-1.43.4.txt @@ -0,0 +1,9 @@ +12:57:50 [5] drill commit f8b700e1d889: plex plexinc/pms-docker:1.43.5.11029-6bf8af3f0 -> plexinc/pms-docker:1.43.4.10903-e5521bd8c (push rc=0) +12:57:58 [5] the box's drill mirror now at f8b700e1 +f8b700e1d889 +13:00:27 [sync] the badge NEVER caught up to plexinc/pms-docker:1.43.4.10903-e5521bd8c in 148.8s — catalog_images = None +13:00:27 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH', 'PLEX_CLAIM'] +13:00:27 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +13:00:47 [1] deployed, controller state=running, pinned={'plex': 'plexinc/pms-docker:1.43.4.10903-e5521bd8c'} +13:00:47 gate: plex is gated — passed as the household (cookie set) +deployed True {"pinned_images": {"plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c"}, "installed_images": {"plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c"}, "catalog_images": null, "live_compose_image_lines": ["image: plexinc/pms-docker:1.43.4.10903-e5521bd8c"], "docker_inspect": ["plex plexinc/pms-docker:1.43.4.10903-e5521bd8c running=true restarts=0"]} diff --git a/documentation/audits/security-lane-2026-10-23/P2-oldctl/2-night-chain-0.340.0.txt b/documentation/audits/security-lane-2026-10-23/P2-oldctl/2-night-chain-0.340.0.txt new file mode 100644 index 00000000..9f16cac6 --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/P2-oldctl/2-night-chain-0.340.0.txt @@ -0,0 +1,15 @@ +13:01:23 [5] the box's drill mirror now at f46e1bc6 +before: {"pinned_images": {"plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c"}, "installed_images": {"plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c"}, "catalog_images": {"plex": "plexinc/pms-docker:1.43.5.11029-6bf8af3f0"}, "live_compose_image_lines": ["image: plexinc/pms-docker:1.43.4.10903-e5521bd8c"], "docker_inspect": ["plex plexinc/pms-docker:1.43.4.10903-e5521bd8c running=true restarts=0"]} +badge before: {'hu': [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}], 'en': [{'title': 'A newer version of this app is available. Select the Update button to start it.', 'text': 'Update available — today'}]} +night chain -> 202 {'data': {'legs': ['db-dump', 'tier2', 'update-leg']}, 'message': 'started', 'ok': True} +the leg's own lines: +2026/10/23 11:01:34 night_chain.go:108: [INFO] [night-chain] manual run started from 172.18.0.6:46254: [db-dump tier2 update-leg] +2026/10/23 11:02:40 night_chain.go:77: [INFO] [night-chain] update-leg: started +2026/10/23 11:02:40 unattended.go:270: [INFO] [update-leg] started (manual-chain): window 02:30, no step starts at or after 16:17 +2026/10/23 11:02:41 unattended.go:316: [INFO] [update-leg] plex: skipped — not_proven +2026/10/23 11:02:41 unattended.go:316: [INFO] [update-leg] privatebin: skipped — stopped_by_household +2026/10/23 11:02:41 unattended.go:248: [INFO] [update-leg] update leg (manual-chain): done=0 undone=0 held=0 failed=0 skipped=2 in 0s [skipped: plex=not_proven, privatebin=stopped_by_household] +2026/10/23 11:02:41 night_chain.go:82: [INFO] [night-chain] update-leg: done in 0s + +after: {"pinned_images": {"plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c"}, "installed_images": {"plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c"}, "catalog_images": {"plex": "plexinc/pms-docker:1.43.5.11029-6bf8af3f0"}, "live_compose_image_lines": ["image: plexinc/pms-docker:1.43.4.10903-e5521bd8c"], "docker_inspect": ["plex plexinc/pms-docker:1.43.4.10903-e5521bd8c running=true restarts=0"]} +state running hold None phase None diff --git a/documentation/audits/security-lane-2026-10-23/P2-oldctl/3-hand-press-0.340.0.txt b/documentation/audits/security-lane-2026-10-23/P2-oldctl/3-hand-press-0.340.0.txt new file mode 100644 index 00000000..8647692c --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/P2-oldctl/3-hand-press-0.340.0.txt @@ -0,0 +1,11 @@ +13:03:07 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} +13:03:07 + 0.0s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None +13:03:08 + 1.0s phase=pulling label=Új verzió letöltése… err=None hold=None +13:03:16 + 9.2s phase=copying label=Az adatok másolása a frissítés előtt… err=None hold=None +13:03:26 + 19.5s phase=starting label=Indítás az új verzióval… err=None hold=None +13:03:27 + 20.5s phase=verifying label=Működés ellenőrzése… err=None hold=None +13:03:32 + 25.6s phase=done label=Frissítve err=None hold=None +{"accepted": true, "final_phase": "done", "update_error": null, "hold_reason": null, "state": "running", "duration_s": 25.7} +after: {"pinned_images": {"plex": "plexinc/pms-docker:1.43.5.11029-6bf8af3f0"}, "installed_images": {"plex": "plexinc/pms-docker:1.43.5.11029-6bf8af3f0"}, "catalog_images": {"plex": "plexinc/pms-docker:1.43.5.11029-6bf8af3f0"}, "live_compose_image_lines": ["image: plexinc/pms-docker:1.43.5.11029-6bf8af3f0@sha256:01bdf0ad6d53198da4331540c1e73ab65db7a72e961fff97ecb0847d46055c5f"], "docker_inspect": ["plex plexinc/pms-docker:1.43.5.11029-6bf8af3f0@sha256:01bdf0ad6d53198da4331540c1e73ab65db7a72e961fff97ecb0847d46055c5f running=true restarts=0"]} +13:03:51 gate: plex is gated — passed as the household (cookie set) +identity 200 {"MediaContainer":{"size":0,"apiVersion":"1.2.3","claimed":false,"machineIdentifier":"f34aaee0f637bb3bf168e5b4e5ce2223c7c40c5f","version":"1.43.5.11029-6bf8af3f0"}} diff --git a/documentation/audits/security-lane-2026-10-23/P2-walk/box-verdict-plex.json b/documentation/audits/security-lane-2026-10-23/P2-walk/box-verdict-plex.json new file mode 100644 index 00000000..7f93c67a --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/P2-walk/box-verdict-plex.json @@ -0,0 +1,227 @@ +{ + "app": "plex", + "walk": "security", + "box_walk_version": 1, + "verdict": "proven", + "venue": "box 9202 (drill catalog), the guarded Update", + "from": { + "plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c" + }, + "to": { + "plex": "plexinc/pms-docker:1.43.5.11029-6bf8af3f0" + }, + "observables_before": { + "pinned_images": { + "plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c" + }, + "installed_images": { + "plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c" + }, + "catalog_images": null, + "live_compose_image_lines": [ + "image: plexinc/pms-docker:1.43.4.10903-e5521bd8c" + ], + "docker_inspect": [ + "plex plexinc/pms-docker:1.43.4.10903-e5521bd8c running=true restarts=0" + ] + }, + "setup_gate_before": { + "browser": { + "status": "302", + "redirect": "https://felhom.enkisfelhom.hu/__gate/start?…" + }, + "script": { + "status": "401", + "redirect": null + } + }, + "seed": "none", + "seed_why": "the first-run claim needs a token minted at plex.tv by a real Plex account; no account exists for this venue", + "drill_commit": "b5e2406d3b49", + "sync_waited_s": 4.5, + "badge_before": { + "hu": [ + { + "title": "Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.", + "text": "Frissítés elérhető — ma" + } + ], + "en": [ + { + "title": "A newer version of this app is available. Select the Update button to start it.", + "text": "Update available — today" + } + ] + }, + "measured_at": "2026-10-23T10:36:22Z", + "update": { + "accepted": true, + "http": "202", + "phases": [ + { + "t": 0.0, + "phase": "backing-up", + "label": "Biztonsági mentés készül a frissítés előtt…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 11.3, + "phase": "safety-dump", + "label": "Adatbázis pillanatkép…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 12.3, + "phase": "pulling", + "label": "Új verzió letöltése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 20.5, + "phase": "copying", + "label": "Az adatok másolása a frissítés előtt…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 30.8, + "phase": "starting", + "label": "Indítás az új verzióval…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 31.8, + "phase": "verifying", + "label": "Működés ellenőrzése…", + "updating": true, + "error": null, + "hold": null + }, + { + "t": 36.9, + "phase": "done", + "label": "Frissítve", + "updating": false, + "error": null, + "hold": null + } + ], + "duration_s": 36.9, + "final_phase": "done", + "update_error": null, + "hold_reason": null, + "state": "running" + }, + "observables_after": { + "pinned_images": { + "plex": "plexinc/pms-docker:1.43.5.11029-6bf8af3f0" + }, + "installed_images": { + "plex": "plexinc/pms-docker:1.43.5.11029-6bf8af3f0" + }, + "catalog_images": { + "plex": "plexinc/pms-docker:1.43.5.11029-6bf8af3f0" + }, + "live_compose_image_lines": [ + "image: plexinc/pms-docker:1.43.5.11029-6bf8af3f0" + ], + "docker_inspect": [ + "plex plexinc/pms-docker:1.43.5.11029-6bf8af3f0 running=true restarts=0" + ] + }, + "badge_after": { + "hu": [ + { + "title": "Ez az alkalmazás a legfrissebb elérhető változatot futtatja.", + "text": "Naprakész" + } + ], + "en": [ + { + "title": "This app is running the newest version available.", + "text": "Up to date" + } + ] + }, + "state_after": "running", + "hold_after": null, + "to_digests": { + "plex": "sha256:01bdf0ad6d53198da4331540c1e73ab65db7a72e961fff97ecb0847d46055c5f" + }, + "four_observables": { + "pinned": { + "plex": "plexinc/pms-docker:1.43.5.11029-6bf8af3f0" + }, + "installed": { + "plex": "plexinc/pms-docker:1.43.5.11029-6bf8af3f0" + }, + "catalog": { + "plex": "plexinc/pms-docker:1.43.5.11029-6bf8af3f0" + }, + "live_compose": [ + "plexinc/pms-docker:1.43.5.11029-6bf8af3f0" + ] + }, + "setup_gate_after": { + "browser": { + "status": "302", + "redirect": "https://felhom.enkisfelhom.hu/__gate/start?…" + }, + "script": { + "status": "401", + "redirect": null + } + }, + "memory": { + "soak_s": 600, + "interval_s": 10, + "samples": 60, + "containers": { + "plex": { + "samples": 60, + "anon_peak_bytes": 229015552, + "limit_bytes": 2147483648, + "anon_peak_pct": 10.7, + "cgroup_peak_pct": 11.5, + "oom_kill_delta": 0, + "oom_killed_flag": false, + "restarts_delta": 0, + "running_all_samples": true, + "cgroup": "/sys/fs/cgroup/system.slice/docker-f699bdec12560064fa6bd64884d941915072974ca27d389bcb3e3b917861ac88.scope" + } + }, + "oom_kills": 0, + "restarts": 0, + "peak_pct": 10.7 + }, + "why": "the security-fix step ran on the box through the guarded Update; healthy, TO on all four observables, no undo, no hold, a clean soak", + "log": [ + "12:35:39 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH', 'PLEX_CLAIM']", + "12:35:40 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}", + "12:36:05 [1] deployed, controller state=running, pinned={'plex': 'plexinc/pms-docker:1.43.4.10903-e5521bd8c'}", + "12:36:05 gate: plex is gated — passed as the household (cookie set)", + "12:36:08 [2] setup gate as installed: {'browser': {'status': '302', 'redirect': 'https://felhom.enkisfelhom.hu/__gate/start?…'}, 'script': {'status': '401', 'redirect': None}}", + "12:36:08 [3] seed: none — the first-run claim needs a token minted at plex.tv by a real Plex account; no account exists for this venue", + "12:36:09 [5] drill commit b5e2406d3b49: plex plexinc/pms-docker:1.43.4.10903-e5521bd8c -> plexinc/pms-docker:1.43.5.11029-6bf8af3f0 (push rc=0)", + "12:36:18 [5] the box's drill mirror now at b5e2406d", + "12:36:23 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'}", + "12:36:23 + 0.0s phase=backing-up label=Biztonsági mentés készül a frissítés előtt… err=None hold=None", + "12:36:34 + 11.3s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None", + "12:36:35 + 12.3s phase=pulling label=Új verzió letöltése… err=None hold=None", + "12:36:43 + 20.5s phase=copying label=Az adatok másolása a frissítés előtt… err=None hold=None", + "12:36:53 + 30.8s phase=starting label=Indítás az új verzióval… err=None hold=None", + "12:36:54 + 31.8s phase=verifying label=Működés ellenőrzése… err=None hold=None", + "12:36:59 + 36.9s phase=done label=Frissítve err=None hold=None", + "12:37:18 [7] memory soak 600 s …", + "12:47:24 [7] soak: peak 10.7% of the limit (anon), kills 0, restarts 0" + ] +} \ No newline at end of file diff --git a/documentation/audits/security-lane-2026-10-23/P2-walk/walk.txt b/documentation/audits/security-lane-2026-10-23/P2-walk/walk.txt new file mode 100644 index 00000000..3a75edbb --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/P2-walk/walk.txt @@ -0,0 +1,23 @@ +12:35:39 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH', 'PLEX_CLAIM'] +12:35:40 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +12:36:05 [1] deployed, controller state=running, pinned={'plex': 'plexinc/pms-docker:1.43.4.10903-e5521bd8c'} +12:36:05 gate: plex is gated — passed as the household (cookie set) +12:36:08 [2] setup gate as installed: {'browser': {'status': '302', 'redirect': 'https://felhom.enkisfelhom.hu/__gate/start?…'}, 'script': {'status': '401', 'redirect': None}} +12:36:08 [3] seed: none — the first-run claim needs a token minted at plex.tv by a real Plex account; no account exists for this venue +12:36:09 [5] drill commit b5e2406d3b49: plex plexinc/pms-docker:1.43.4.10903-e5521bd8c -> plexinc/pms-docker:1.43.5.11029-6bf8af3f0 (push rc=0) +12:36:18 [5] the box's drill mirror now at b5e2406d +12:36:23 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'} +12:36:23 + 0.0s phase=backing-up label=Biztonsági mentés készül a frissítés előtt… err=None hold=None +12:36:34 + 11.3s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None +12:36:35 + 12.3s phase=pulling label=Új verzió letöltése… err=None hold=None +12:36:43 + 20.5s phase=copying label=Az adatok másolása a frissítés előtt… err=None hold=None +12:36:53 + 30.8s phase=starting label=Indítás az új verzióval… err=None hold=None +12:36:54 + 31.8s phase=verifying label=Működés ellenőrzése… err=None hold=None +12:36:59 + 36.9s phase=done label=Frissítve err=None hold=None +12:37:18 [7] memory soak 600 s … +12:47:24 [7] soak: peak 10.7% of the limit (anon), kills 0, restarts 0 +12:47:24 [9] verdict proven -> /mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/security-lane-2026-10-23/P2-walk/verdict.json +12:47:24 RESULT proven — the security-fix step ran on the box through the guarded Update; healthy, TO on all four observables, no undo, no hold, a clean soak +12:47:33 [X] stop -> 200 {'ok': True, 'message': 'Stack plex stop completed'} +12:48:05 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'plex', 'volumes_removed': ['plex_plex_config', 'plex_plex_transcode'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'us +12:48:13 [X] after remove: deployed=False leftovers='/opt/docker/stacks/plex' diff --git a/documentation/audits/security-lane-2026-10-23/P2-walk/writer.txt b/documentation/audits/security-lane-2026-10-23/P2-walk/writer.txt new file mode 100644 index 00000000..3bf95e05 --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/P2-walk/writer.txt @@ -0,0 +1 @@ +WROTE plex: {'plex': 'plexinc/pms-docker:1.43.4.10903-e5521bd8c'} -> {'plex': 'plexinc/pms-docker:1.43.5.11029-6bf8af3f0'} peak 10.7% marks {'files_may_change': False, 'needs_person': None, 'memory_tight': False} diff --git a/documentation/audits/security-lane-2026-10-23/P3-newctl/0-reinstall-at-1.43.4.txt b/documentation/audits/security-lane-2026-10-23/P3-newctl/0-reinstall-at-1.43.4.txt new file mode 100644 index 00000000..5e344a5f --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/P3-newctl/0-reinstall-at-1.43.4.txt @@ -0,0 +1,21 @@ +13:04:15 [X] stop -> 200 {'ok': True, 'message': 'Stack plex stop completed'} +13:04:47 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'plex', 'volumes_removed': ['plex_plex_config', 'plex_plex_transcode'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'us +13:04:56 [X] after remove: deployed=False leftovers='/opt/docker/stacks/plex' +remote: . Processing 1 references +remote: Processed 1 references in total +13:05:05 [5] the box's drill mirror now at 42945c94 +13:07:34 [sync] the badge NEVER caught up to plexinc/pms-docker:1.43.4.10903-e5521bd8c in 149.0s — catalog_images = None +13:07:34 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH', 'PLEX_CLAIM'] +13:07:34 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'} +13:07:54 [1] deployed, controller state=running, pinned={'plex': 'plexinc/pms-docker:1.43.4.10903-e5521bd8c'} +deploy True +13:07:55 gate: plex is gated — passed as the household (cookie set) +remote: . Processing 1 references +remote: Processed 1 references in total +17e0da0 Revert "DRILL plex: compose image line back to 1.43.4 ONLY (the ladder untouched) — a fresh install at FROM for the Part 3 proof; reverted right after" +42945c9 DRILL plex: compose image line back to 1.43.4 ONLY (the ladder untouched) — a fresh install at FROM for the Part 3 proof; reverted right after +f46e1bc DRILL plex: security_fix step 1.43.4 -> 1.43.5 written by upgrade-test.py --write-ladder --security (D39 = B proof) + +13:08:04 [5] the box's drill mirror now at 17e0da02 +obs {"pinned_images": {"plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c"}, "installed_images": {"plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c"}, "catalog_images": {"plex": "plexinc/pms-docker:1.43.5.11029-6bf8af3f0"}, "live_compose_image_lines": ["image: plexinc/pms-docker:1.43.4.10903-e5521bd8c"], "docker_inspect": ["plex plexinc/pms-docker:1.43.4.10903-e5521bd8c running=true restarts=0"]} +badge {'hu': [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}], 'en': [{'title': 'A newer version of this app is available. Select the Update button to start it.', 'text': 'Update available — today'}]} diff --git a/documentation/audits/security-lane-2026-10-23/P3-newctl/1-night-chain-0.341.0-rc.1.txt b/documentation/audits/security-lane-2026-10-23/P3-newctl/1-night-chain-0.341.0-rc.1.txt new file mode 100644 index 00000000..19ff5e76 --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/P3-newctl/1-night-chain-0.341.0-rc.1.txt @@ -0,0 +1,17 @@ +13:12:03 [5] the box's drill mirror now at 17e0da02 +before: {"pinned_images": {"plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c"}, "installed_images": {"plex": "plexinc/pms-docker:1.43.4.10903-e5521bd8c"}, "catalog_images": {"plex": "plexinc/pms-docker:1.43.5.11029-6bf8af3f0"}, "live_compose_image_lines": ["image: plexinc/pms-docker:1.43.4.10903-e5521bd8c"], "docker_inspect": ["plex plexinc/pms-docker:1.43.4.10903-e5521bd8c running=true restarts=0"]} +badge before: {'hu': [{'title': 'Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.', 'text': 'Frissítés elérhető — ma'}], 'en': [{'title': 'A newer version of this app is available. Select the Update button to start it.', 'text': 'Update available — today'}]} +night chain -> 202 {'data': {'legs': ['db-dump', 'tier2', 'update-leg']}, 'message': 'started', 'ok': True} +the leg's own lines: +2026/10/23 11:12:13 night_chain.go:108: [INFO] [night-chain] manual run started from 172.18.0.6:38520: [db-dump tier2 update-leg] +2026/10/23 11:13:15 night_chain.go:77: [INFO] [night-chain] update-leg: started +2026/10/23 11:13:15 unattended.go:279: [INFO] [update-leg] started (manual-chain): window 02:30, no step starts at or after 16:28 +2026/10/23 11:13:15 unattended.go:345: [INFO] [update-leg] plex: step pressed plex=plexinc/pms-docker:1.43.4.10903-e5521bd8c → plex=plexinc/pms-docker:1.43.5.11029-6bf8af3f0 +2026/10/23 11:13:15 unattended.go:351: [INFO] [update-leg] plex: security fix CVE-2026-96652,CVE-2026-96654,CVE-2026-96655,CVE-2026-96656 taken (plex=plexinc/pms-docker:1.43.4.10903-e5521bd8c → plex=plexinc/pms-docker:1.43.5.11029-6bf8af3f0) +2026/10/23 11:13:45 unattended.go:359: [INFO] [update-leg] plex: step ended done after 30.1 s +2026/10/23 11:13:45 unattended.go:325: [INFO] [update-leg] privatebin: skipped — stopped_by_household +2026/10/23 11:13:45 unattended.go:257: [INFO] [update-leg] update leg (manual-chain): done=1 undone=0 held=0 failed=0 skipped=1 in 30s [skipped: privatebin=stopped_by_household] [security fix plex: CVE-2026-96652,CVE-2026-96654,CVE-2026-96655,CVE-2026-96656 done] +2026/10/23 11:13:45 night_chain.go:82: [INFO] [night-chain] update-leg: done in 30s + +after: {"pinned_images": {"plex": "plexinc/pms-docker:1.43.5.11029-6bf8af3f0"}, "installed_images": {"plex": "plexinc/pms-docker:1.43.5.11029-6bf8af3f0"}, "catalog_images": {"plex": "plexinc/pms-docker:1.43.5.11029-6bf8af3f0"}, "live_compose_image_lines": ["image: plexinc/pms-docker:1.43.5.11029-6bf8af3f0@sha256:01bdf0ad6d53198da4331540c1e73ab65db7a72e961fff97ecb0847d46055c5f"], "docker_inspect": ["plex plexinc/pms-docker:1.43.5.11029-6bf8af3f0@sha256:01bdf0ad6d53198da4331540c1e73ab65db7a72e961fff97ecb0847d46055c5f running=true restarts=0"]} +state running hold None phase done diff --git a/documentation/audits/security-lane-2026-10-23/P3-newctl/2-update-log-backup-first.txt b/documentation/audits/security-lane-2026-10-23/P3-newctl/2-update-log-backup-first.txt new file mode 100644 index 00000000..536f1cb7 --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/P3-newctl/2-update-log-backup-first.txt @@ -0,0 +1,28 @@ +2026/10/23 11:13:15 tier2.go:507: [INFO] [backup] Tier 2 copied plex → /mnt/sizing-bench/backups/secondary/plex (2.3 MB, 0 leg(s), 0s) +2026/10/23 11:13:15 update.go:537: [INFO] [stacks] update plex: accepted — guarded update started +2026/10/23 11:13:15 unattended.go:345: [INFO] [update-leg] plex: step pressed plex=plexinc/pms-docker:1.43.4.10903-e5521bd8c → plex=plexinc/pms-docker:1.43.5.11029-6bf8af3f0 +2026/10/23 11:13:15 unattended.go:351: [INFO] [update-leg] plex: security fix CVE-2026-96652,CVE-2026-96654,CVE-2026-96655,CVE-2026-96656 taken (plex=plexinc/pms-docker:1.43.4.10903-e5521bd8c → plex=plexinc/pms-docker:1.43.5.11029-6bf8af3f0) +2026/10/23 11:13:15 update.go:1378: [INFO] [stacks] update plex: phase checking +2026/10/23 11:13:15 update.go:795: [INFO] [stacks] update plex: ladder — the last step (1 of 1) — the catalog's current definition +2026/10/23 11:13:15 update.go:823: [INFO] [stacks] update plex: precondition met — Tier 2 (second drive) copy from 2026-10-23T11:13:04Z (0s old, limit 24h0m0s) +2026/10/23 11:13:15 update.go:1378: [INFO] [stacks] update plex: phase safety-dump +2026/10/23 11:13:16 update_guard.go:497: [INFO] [backup] update safety dump for plex: the app has no database — nothing to copy (no-op) +2026/10/23 11:13:16 update.go:855: [INFO] [stacks] update plex: safety dump done (0 file(s)) [] +2026/10/23 11:13:17 undo.go:287: [INFO] [stacks] update plex: the undo copy will hold 2 named volume(s), 2.8 MiB +2026/10/23 11:13:17 update.go:1378: [INFO] [stacks] update plex: phase pinning +2026/10/23 11:13:17 pin.go:385: [INFO] [stacks] update plex: pin advanced to /opt/docker/felhom-controller/data/catalog-cache/templates/plex/docker-compose.yml (plex=plexinc/pms-docker:1.43.5.11029-6bf8af3f0) +2026/10/23 11:13:17 update.go:1378: [INFO] [stacks] update plex: phase pulling +2026/10/23 11:13:28 update.go:1378: [INFO] [stacks] update plex: phase copying +2026/10/23 11:13:38 update.go:1378: [INFO] [stacks] update plex: phase copying +2026/10/23 11:13:38 undo.go:437: [INFO] [stacks] update plex: copied plex_plex_config → plex_plex_config.pre-update-20261023T111338Z in 520ms +2026/10/23 11:13:38 update.go:1378: [INFO] [stacks] update plex: phase copying +2026/10/23 11:13:39 undo.go:437: [INFO] [stacks] update plex: copied plex_plex_transcode → plex_plex_transcode.pre-update-20261023T111338Z in 465ms +2026/10/23 11:13:39 update.go:1378: [INFO] [stacks] update plex: phase starting +2026/10/23 11:13:39 update.go:1378: [INFO] [stacks] update plex: phase verifying +2026/10/23 11:13:44 update.go:1006: [INFO] [stacks] update plex: healthy after 5s (the app's health check passed) +2026/10/23 11:13:45 update.go:1043: [INFO] [stacks] update plex: DONE in 29s +2026/10/23 11:13:45 image_retention.go:238: [INFO] [stacks] image retention (update of plex): pass over 40 image(s) of [plexinc/pms-docker] — 0 candidate(s), 0 deleted, the rest kept +2026/10/23 11:13:45 unattended.go:359: [INFO] [update-leg] plex: step ended done after 30.1 s +2026/10/23 11:13:45 unattended.go:257: [INFO] [update-leg] update leg (manual-chain): done=1 undone=0 held=0 failed=0 skipped=1 in 30s [skipped: privatebin=stopped_by_household] [security fix plex: CVE-2026-96652,CVE-2026-96654,CVE-2026-96655,CVE-2026-96656 do +identity 200 {"MediaContainer":{"size":0,"apiVersion":"1.2.3","claimed":false,"machineIdentifier":"de0d1cd3ffa436835af296505a1e2534be391d1f","version":"1.43.5.11029-6bf8af3f +state running health None diff --git a/documentation/audits/security-lane-2026-10-23/P4-release/box-walk-0.341.0.txt b/documentation/audits/security-lane-2026-10-23/P4-release/box-walk-0.341.0.txt new file mode 100644 index 00000000..54d9e02a --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/P4-release/box-walk-0.341.0.txt @@ -0,0 +1,27 @@ +### root@192.168.0.154 +containers=10 unhealthy=0 restarting=0 exited=[] gitea.dooplex.hu/admin/felhom-controller:0.341.0 Up 35 seconds (healthy) +started=2026-10-23T11:15:27.242236608Z +errors=0 info=126 +2026/10/23 11:15:28 [INFO] Self-update enabled (check every 6h, auto-update: false, auto-update time: 04:30) +2026/10/23 11:15:28 [INFO] [scheduler] Registered periodic job: selfupdate-check (every 6h0m0s) +2026/10/23 11:15:59 [INFO] [selfupdate] Current version 0.341.0 is up to date +2026/10/23 11:15:29 [INFO] [sync] Initial sync: Sablonok frissítve — a katalógus új változata betöltve; a telepített alkalmazások fájljai nem változt +### hp +containers=24 unhealthy=0 restarting=0 exited=[] gitea.dooplex.hu/admin/felhom-controller:0.341.0 Up 36 seconds (healthy) +started=2026-10-23T11:15:28.047379649Z +errors=0 info=135 +2026/10/23 11:15:29 deploy.go:1442: [WARN] [stacks] Stack bookstack: new field ADMIN_PASSWORD (type=password) requires manual configuration +2026/10/23 11:15:29 deploy.go:1442: [WARN] [stacks] Stack calibre-web: new field ADMIN_PASSWORD (type=password) requires manual configuration +2026/10/23 11:15:29 deploy.go:1442: [WARN] [stacks] Stack plex: new field PLEX_CLAIM (type=text) requires manual configuration +2026/10/23 11:15:29 main.go:833: [INFO] Self-update enabled (check every 6h, auto-update: false, auto-update time: 04:30) +2026/10/23 11:15:29 scheduler.go:120: [INFO] [scheduler] Registered periodic job: selfupdate-check (every 6h0m0s) +2026/10/23 11:16:01 updater.go:196: [INFO] [selfupdate] Current version 0.341.0 is up to date +2026/10/23 11:15:30 sync.go:142: [INFO] [sync] Initial sync: Sablonok frissítve — a katalógus új változata betöltve; a telepített alkalmazások fájljai +### felhom-pve +containers=9 unhealthy=0 restarting=0 exited=[] gitea.dooplex.hu/admin/felhom-controller:0.341.0 Up 40 seconds (healthy) +started=2026-10-23T11:15:11.64495822Z +errors=0 info=141 +2026/10/23 11:15:12 [INFO] Self-update enabled (check every 6h, auto-update: false, auto-update time: 04:30) +2026/10/23 11:15:12 [INFO] [scheduler] Registered periodic job: selfupdate-check (every 6h0m0s) +2026/10/23 11:15:44 [INFO] [selfupdate] Current version 0.341.0 is up to date +2026/10/23 11:15:13 [INFO] [sync] Initial sync: Sablonok frissítve — a katalógus új változata betöltve; a telepített alkalmazások fájljai nem változt diff --git a/documentation/audits/security-lane-2026-10-23/P4-release/global-floor.txt b/documentation/audits/security-lane-2026-10-23/P4-release/global-floor.txt new file mode 100644 index 00000000..7e409704 --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/P4-release/global-floor.txt @@ -0,0 +1,10 @@ +2026-10-23T11:24:47Z +HTTP/1.1 303 See Other +Location: /configuration?flash=floor_set +Effective floor: v0.341.0 — source: DB (hub_settings) ; env fallback would be v0.120.0 Save global floor… DB override: v +clear tester-1 Location: /customers/tester-1?flash=floor_set +clear demo-hp Location: /customers/demo-hp?flash=floor_set +clear demo-felhom Location: /customers/demo-felhom?flash=floor_set +2026/10/23 13:15:19 [INFO] managed floor SERVED for tester-1: floor 0.341.0, agent requirement "0.158.1" from declared (golden 0.331.0) +2026/10/23 13:15:20 [INFO] managed floor SERVED for demo-felhom: floor 0.341.0, agent requirement "0.158.1" from declared (golden 0.331.0) +2026/10/23 13:15:20 [INFO] managed floor SERVED for demo-hp: floor 0.341.0, agent requirement "0.158.1" from declared (golden 0.331.0) diff --git a/documentation/audits/security-lane-2026-10-23/P4-release/https-only.txt b/documentation/audits/security-lane-2026-10-23/P4-release/https-only.txt new file mode 100644 index 00000000..8682d689 --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/P4-release/https-only.txt @@ -0,0 +1,33 @@ +control NEGATIVE (a stub answering 200 over plain http): convicted, as it must (200 -) +== tester-1 (enkicsifelhom.hu): 6 public name(s) + OK control POSITIVE http://felhom.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://felhom.enkicsifelhom.hu/r1033-check?q=1 + OK http://files.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://files.enkicsifelhom.hu/r1033-check?q=1 + OK http://no-such-name-r1033.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://no-such-name-r1033.enkicsifelhom.hu/r1033-check?q=1 + OK http://paperless.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://paperless.enkicsifelhom.hu/r1033-check?q=1 + OK http://paste.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://paste.enkicsifelhom.hu/r1033-check?q=1 + OK http://wiki.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://wiki.enkicsifelhom.hu/r1033-check?q=1 +== demo-hp (enkisfelhom.hu): 14 public name(s) + OK http://arcade.enkisfelhom.hu/r1033-check?q=1 -> 301 https://arcade.enkisfelhom.hu/r1033-check?q=1 + OK http://books.enkisfelhom.hu/r1033-check?q=1 -> 301 https://books.enkisfelhom.hu/r1033-check?q=1 + OK http://docs.enkisfelhom.hu/r1033-check?q=1 -> 301 https://docs.enkisfelhom.hu/r1033-check?q=1 + OK http://felhom.enkisfelhom.hu/r1033-check?q=1 -> 301 https://felhom.enkisfelhom.hu/r1033-check?q=1 + OK http://files.enkisfelhom.hu/r1033-check?q=1 -> 301 https://files.enkisfelhom.hu/r1033-check?q=1 + OK http://gist.enkisfelhom.hu/r1033-check?q=1 -> 301 https://gist.enkisfelhom.hu/r1033-check?q=1 + OK http://ha.enkisfelhom.hu/r1033-check?q=1 -> 301 https://ha.enkisfelhom.hu/r1033-check?q=1 + OK http://no-such-name-r1033.enkisfelhom.hu/r1033-check?q=1 -> 301 https://no-such-name-r1033.enkisfelhom.hu/r1033-check?q=1 + OK http://papir.enkisfelhom.hu/r1033-check?q=1 -> 301 https://papir.enkisfelhom.hu/r1033-check?q=1 + OK http://paste.enkisfelhom.hu/r1033-check?q=1 -> 301 https://paste.enkisfelhom.hu/r1033-check?q=1 + OK http://pdf.enkisfelhom.hu/r1033-check?q=1 -> 301 https://pdf.enkisfelhom.hu/r1033-check?q=1 + OK http://plex.enkisfelhom.hu/r1033-check?q=1 -> 301 https://plex.enkisfelhom.hu/r1033-check?q=1 + OK http://travel.enkisfelhom.hu/r1033-check?q=1 -> 301 https://travel.enkisfelhom.hu/r1033-check?q=1 + OK http://wiki.enkisfelhom.hu/r1033-check?q=1 -> 301 https://wiki.enkisfelhom.hu/r1033-check?q=1 +== demo-felhom (demo-felhom.eu): 8 public name(s) + OK http://felhom.demo-felhom.eu/r1033-check?q=1 -> 301 https://felhom.demo-felhom.eu/r1033-check?q=1 + OK http://files.demo-felhom.eu/r1033-check?q=1 -> 301 https://files.demo-felhom.eu/r1033-check?q=1 + OK http://gist.demo-felhom.eu/r1033-check?q=1 -> 301 https://gist.demo-felhom.eu/r1033-check?q=1 + OK http://no-such-name-r1033.demo-felhom.eu/r1033-check?q=1 -> 301 https://no-such-name-r1033.demo-felhom.eu/r1033-check?q=1 + OK http://prowlarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://prowlarr.demo-felhom.eu/r1033-check?q=1 + OK http://radarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://radarr.demo-felhom.eu/r1033-check?q=1 + OK http://sonarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://sonarr.demo-felhom.eu/r1033-check?q=1 + OK http://torrent.demo-felhom.eu/r1033-check?q=1 -> 301 https://torrent.demo-felhom.eu/r1033-check?q=1 +== every public name redirects http -> https diff --git a/documentation/audits/security-lane-2026-10-23/P4-release/login.txt b/documentation/audits/security-lane-2026-10-23/P4-release/login.txt new file mode 100644 index 00000000..42d7cfdb --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/P4-release/login.txt @@ -0,0 +1,3 @@ +9201 login ok, /api/stacks 200 +login failed: no session cookie +n100 /api/health 200tester-1 (BASE .148) login ok, /api/stacks 200 diff --git a/documentation/audits/security-lane-2026-10-23/P4-release/per-box-floors.txt b/documentation/audits/security-lane-2026-10-23/P4-release/per-box-floors.txt new file mode 100644 index 00000000..c12e5fc0 --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/P4-release/per-box-floors.txt @@ -0,0 +1,3 @@ +tester-1 HTTP/1.1 303 See Other Location: /customers/tester-1?flash=floor_set +demo-hp HTTP/1.1 303 See Other Location: /customers/demo-hp?flash=floor_set +demo-felhom HTTP/1.1 303 See Other Location: /customers/demo-felhom?flash=floor_set diff --git a/documentation/audits/security-lane-2026-10-23/README.md b/documentation/audits/security-lane-2026-10-23/README.md new file mode 100644 index 00000000..de4799ab --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/README.md @@ -0,0 +1,74 @@ +# D39 = B — the security-fix lane, built and proven (2026-10-23) + +Decision: `architecture/09-update-architecture.md` §3 decision 255 (the operator started the D39 brief ~12:00; R-1239 +closed). Read before the work: `09` §3 decisions 13–17, 22, 25–26, 52 and §6.4 parts 4–7; `00-capability-map.md` (the +B rows); `13-threat-model.md`; `runbooks/target-selection.md`; `runbooks/publish-train-rules.md`. + +Baselines at start: catalog `cde26ab`, controller `32e61d5` (0.340.0, global floor 06:41 UTC — read from the hub before +the first controller commit), felhom.eu `4b9b0af3`. Machines: DooPlex (builds, the writer), demo-hp (9202 scratch; the +release on 9201), Tester 1, demo-felhom (release only). Not touched: ep0, any household box. + +## Venue + +9202 was pointed at the drill catalog through its LOCAL mirror (`tools/repoint.py`, copied from +`audits/burndown-3-2026-10-17/tools/`, R-992 — no catalog credential on the box): `repoint-drill.txt`. The drill was +reset to live `main` (`cde26ab`) first. 9202's dashboard password is its own (`REHEARSAL_2026_10_20_9202_PW`; the +demo-hp one was refused — five failed logins hit the one-minute limit; `operations/nodes.md` corrected). + +## Part 2 — the lane on the old controller (9202 on 0.340.0) + +| Step | Result | File | +|---|---|---| +| First try of the negative control | `inconclusive` — the install was refused before anything was made (Plex wants the drive itself as `HDD_PATH`); the walk gained `--value` | `P2-negative-try1-install-refused/` | +| **Negative control**: FROM Plex 1.43.4.10903, TO `busybox:1.36.1` (exits at once) | **`failed`** — backing-up → … → verifying → undoing → **undone** after 335 s; Plex 1.43.4 back | `P2-negative/walk.txt`, `box-verdict-plex.json` | +| The writer on that walk | **refused**: „the box walk is 'security' with verdict 'failed' — only a PROVEN `box_walk.py --security` walk is written" | `P2-negative/writer-refusal.txt` | +| **The walk**: Plex 1.43.4.10903-e5521bd8c → 1.43.5.11029-6bf8af3f0 | **`proven`** — done in 36.9 s, no undo, no hold; pinned = installed = catalog = live compose = running = TO; digest `sha256:01bdf0ad6d53198da4331540c1e73ab65db7a72e961fff97ecb0847d46055c5f` (the registry's, re-read by the writer); soak 600 s, 60 samples, anon peak 10.7 % of 2 GiB (cgroup peak 11.5 %), 0 OOM kills, 0 restarts; `seed: none` (Plex's fixture is a `NoRoute` stub) | `P2-walk/` | +| Setup gate (unclaimed Plex) | a browser (`Accept: text/html`) → 302 to `felhom./__gate/start`; a script → 401; the same after the update | `P2-walk/box-verdict-plex.json` | +| The writer, into the DRILL only | wrote the `security_fix` entry: ids CVE-2026-96652/-96654/-96655/-96656; advisory Plex's notice `https://forums.plex.tv/t/important-security-update-for-plex-media-server-v1-43-2-and-earlier/942319` (the one CISA KEV cites, all four added 2026-10-20 — `cisa-kev-plex2shell.json`, from CISA's own feed); `fixed_in` 1.43.3.10861; the gate accepts it with its WARN | `P2-walk/writer.txt`; drill `f46e1bc` | +| Old controller, night (`POST /api/debug/backup/night-chain`) | **`[update-leg] plex: skipped — not_proven`**; Plex stays 1.43.4 (control: the same leg judged privatebin) | `P2-oldctl/2-night-chain-0.340.0.txt` | +| Old controller, hand press | **done** in 25.7 s; Plex 1.43.5 at the step's digest; `/identity` 200 with 1.43.5 | `P2-oldctl/3-hand-press-0.340.0.txt` | + +**How Plex was put back at 1.43.4 for Part 3:** removed through the product (with its data); drill commit `42945c9` +moved ONLY the compose's image line back to 1.43.4 (the ladder line untouched); a fresh install pinned 1.43.4; `17e0da0` +reverted that commit, so the step is the head again and the badge read „Frissítés elérhető" (`P3-newctl/0-…`). + +## Part 3 — the new controller (0.341.0-rc.1 on 9202) + +Controller `63dcccf` (CI run 2264 / #471 success), image `0.341.0-rc.1` set by hand (9202's self-update is off). The +night chain: `[update-leg] plex: step pressed …`, **`[update-leg] plex: security fix +CVE-2026-96652,CVE-2026-96654,CVE-2026-96655,CVE-2026-96656 taken (plex=…1.43.4… → plex=…1.43.5…)`**, `step ended done +after 30.1 s`; the summary line ends `[security fix plex: … done]`. Backup first: the update's precondition met by a +second-drive copy 0 s old, the undo copy of 2 named volumes (2.8 MiB), then the pin. `/identity` 200 with 1.43.5 +(`P3-newctl/1-…`, `2-…`). + +## Part 4 — release 0.341.0 + +Image `0.341.0` (`sha256:8aa4ce60…c0bc`) from `63dcccf`; per-box floors on tester-1, demo-hp, demo-felhom (303 +`floor_set`); all three ran 0.341.0 by 11:15:31 UTC. Box walk: 0 unhealthy, 0 restarting, 0 exited, 0 `[ERROR]` lines since +the start (126 / 135 / 141 `[INFO]` lines — the control); selfupdate „0.341.0 is up to date"; the https-only check passes +on every public name; the dashboard login works on demo-hp and Tester 1 (`/api/stacks` 200), the N100's `/api/health` +200 (`P4-release/`). **The operator said yes in chat; the global floor 0.341.0 / MinAgent 0.158.1 was set at 11:24:47 +UTC** (303 `floor_set`; the running hub's page reads „Effective floor: v0.341.0 — source: DB"); the per-box floors +cleared. + +## Teardown (three layers) + +- **Machine (9202):** Plex removed through the product (no plex container or volume left); `controller.yaml` restored + byte-identical (`cmp`) and the catalog cache re-cloned from the live catalog (R-615); this run's saved copy deleted + after the `cmp`; image `0.341.0`. 34 older saved copies from earlier sessions stay — filed R-1240. The drill + repository keeps this run's commits as the record. +- **Host (demo-hp):** nothing provisioned. +- **Hub:** the global floor 0.341.0 (the operator's yes); per-box floors cleared. + +## Observations + +- FILED: R-1240 — 9202 holds 34 saved `controller.yaml` copies with the catalog token. +- FIXED without a row: drill commits never reached a box pointed at the drill's local mirror (`box_walk.refresh_box_mirror`); + two stale catalog tests (9202 address, gate count + missing sibling in the shallow clone); Tester 1's guest moved + `.101` → `.148` and the box walk had it typed (now read from the guest); `operations/nodes.md` (9202's password, + Tester 1's address). +- NOT-A-FINDING: the writer's own engine-major check is redundant with `check_entry` (two layers on purpose; the test + asserts the refusal); `/opt/docker/stacks/plex` keeps the catalog's template files after a removal, as every catalog + app's folder does (72); the demo-hp start WARNs „new field … requires manual configuration" come from code 0.341.0 did + not change; a waiter of mine matched its own command line (a known trap, memory `pgrep-waiter-matches-itself`) and + was stopped. diff --git a/documentation/audits/security-lane-2026-10-23/cisa-kev-plex2shell.json b/documentation/audits/security-lane-2026-10-23/cisa-kev-plex2shell.json new file mode 100644 index 00000000..aff30eab --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/cisa-kev-plex2shell.json @@ -0,0 +1,70 @@ +{ + "catalogVersion": "2026.10.21", + "dateReleased": "2026-10-21T14:04:39.8983Z", + "entries": [ + { + "cveID": "CVE-2026-96656", + "vendorProject": "Plex", + "product": "Media Server", + "vulnerabilityName": "Plex Media Server External Control of File Name or Path Vulnerability", + "dateAdded": "2026-10-20", + "shortDescription": "Plex Media Server contains an external control of file name or path vulnerability which can allow an admin user to write arbitrary files that may be executed on load. This vulnerability can be chained with CVE-2026-96652, CVE-2026-96654, and CVE-2026-96655.", + "requiredAction": "Apply mitigations in accordance with vendor instructions, ensuring compliance with CISA\u2019s BOD 26-04 Prioritizing Security Updates Based on Risk (see URL in Notes) guidance and CISA\u2019s \u201cForensics Triage Requirements\u201d (see URL in Notes). Follow applicable BOD 26-04 guidance for cloud services or discontinue use of the product if mitigations are unavailable. Stakeholders are responsible for evaluating each asset's internet exposure and ensuring adherence to BOD 26-04 patching guidelines.", + "dueDate": "2026-10-23", + "knownRansomwareCampaignUse": "Unknown", + "forensicTriage": "Yes", + "notes": "https://forums.plex.tv/t/important-security-update-for-plex-media-server-v1-43-2-and-earlier/942319 ; ; BOD 26-04: https://www.cisa.gov/news-events/directives/bod-26-04-prioritizing-security-updates-based-risk ; Forensics Triage Requirements: https://www.cisa.gov/news-events/directives/bod-26-04-implementation-guidance-prioritizing-security-updates-based-risk ; https://nvd.nist.gov/vuln/detail/CVE-2026-96656", + "cwes": [ + "CWE-73" + ] + }, + { + "cveID": "CVE-2026-96655", + "vendorProject": "Plex", + "product": "Media Server", + "vulnerabilityName": "Plex Media Server Server-Side Request Forgery (SSRF) Vulnerability", + "dateAdded": "2026-10-20", + "shortDescription": "Plex Media Server contains a server-side request forgery (SSRF) vulnerability that could allow an authenticated user to request arbitrary internal or external addresses via the '/video/:/transcode/universal' path parameter. This vulnerability can be chained with CVE-2026-96652, CVE-2026-96654, and CVE-2026-96656.", + "requiredAction": "Apply mitigations in accordance with vendor instructions, ensuring compliance with CISA\u2019s BOD 26-04 Prioritizing Security Updates Based on Risk (see URL in Notes) guidance and CISA\u2019s \u201cForensics Triage Requirements\u201d (see URL in Notes). Follow applicable BOD 26-04 guidance for cloud services or discontinue use of the product if mitigations are unavailable. Stakeholders are responsible for evaluating each asset's internet exposure and ensuring adherence to BOD 26-04 patching guidelines.", + "dueDate": "2026-11-03", + "knownRansomwareCampaignUse": "Unknown", + "forensicTriage": "No", + "notes": "https://forums.plex.tv/t/important-security-update-for-plex-media-server-v1-43-2-and-earlier/942319 ; ; BOD 26-04: https://www.cisa.gov/news-events/directives/bod-26-04-prioritizing-security-updates-based-risk ; Forensics Triage Requirements: https://www.cisa.gov/news-events/directives/bod-26-04-implementation-guidance-prioritizing-security-updates-based-risk ; https://nvd.nist.gov/vuln/detail/CVE-2026-96655", + "cwes": [ + "CWE-918" + ] + }, + { + "cveID": "CVE-2026-96654", + "vendorProject": "Plex", + "product": "Media Server", + "vulnerabilityName": "Plex Media Server Improper Neutralization of Encoded URI Schemes in a Web Page Vulnerability", + "dateAdded": "2026-10-20", + "shortDescription": "Plex Media Server contains an improper neutralization of encoded URI schemes in a web page vulnerability that could allow an attacker to call other plugins' functions and supply their own parameters. This vulnerability can be chained with CVE-2026-96652, CVE-2026-96655, and CVE-2026-96656.", + "requiredAction": "Apply mitigations in accordance with vendor instructions, ensuring compliance with CISA\u2019s BOD 26-04 Prioritizing Security Updates Based on Risk (see URL in Notes) guidance and CISA\u2019s \u201cForensics Triage Requirements\u201d (see URL in Notes). Follow applicable BOD 26-04 guidance for cloud services or discontinue use of the product if mitigations are unavailable. Stakeholders are responsible for evaluating each asset's internet exposure and ensuring adherence to BOD 26-04 patching guidelines.", + "dueDate": "2026-10-23", + "knownRansomwareCampaignUse": "Unknown", + "forensicTriage": "No", + "notes": "https://forums.plex.tv/t/important-security-update-for-plex-media-server-v1-43-2-and-earlier/942319 ; ; BOD 26-04: https://www.cisa.gov/news-events/directives/bod-26-04-prioritizing-security-updates-based-risk ; Forensics Triage Requirements: https://www.cisa.gov/news-events/directives/bod-26-04-implementation-guidance-prioritizing-security-updates-based-risk ; https://nvd.nist.gov/vuln/detail/CVE-2026-96654", + "cwes": [ + "CWE-84" + ] + }, + { + "cveID": "CVE-2026-96652", + "vendorProject": "Plex", + "product": "Media Server", + "vulnerabilityName": "Plex Media Server Server-Side Request Forgery (SSRF) Vulnerability", + "dateAdded": "2026-10-20", + "shortDescription": "Plex Media Server contains a server-side request forgery (SSRF) vulnerability which could allow an attacker using any X-Plex-Token value to include a full URL in the 'protocol' parameter and force the Plex server to POST to the attacker's chosen destination. This vulnerability can be chained with CVE-2026-96654, CVE-2026-96655, and CVE-2026-96656. ", + "requiredAction": "Apply mitigations in accordance with vendor instructions, ensuring compliance with CISA\u2019s BOD 26-04 Prioritizing Security Updates Based on Risk (see URL in Notes) guidance and CISA\u2019s \u201cForensics Triage Requirements\u201d (see URL in Notes). Follow applicable BOD 26-04 guidance for cloud services or discontinue use of the product if mitigations are unavailable. Stakeholders are responsible for evaluating each asset's internet exposure and ensuring adherence to BOD 26-04 patching guidelines.", + "dueDate": "2026-11-03", + "knownRansomwareCampaignUse": "Unknown", + "forensicTriage": "No", + "notes": "https://forums.plex.tv/t/important-security-update-for-plex-media-server-v1-43-2-and-earlier/942319 ; ; BOD 26-04: https://www.cisa.gov/news-events/directives/bod-26-04-prioritizing-security-updates-based-risk ; Forensics Triage Requirements: https://www.cisa.gov/news-events/directives/bod-26-04-implementation-guidance-prioritizing-security-updates-based-risk ; https://nvd.nist.gov/vuln/detail/CVE-2026-96652", + "cwes": [ + "CWE-918" + ] + } + ] +} \ No newline at end of file diff --git a/documentation/audits/security-lane-2026-10-23/repoint-drill.txt b/documentation/audits/security-lane-2026-10-23/repoint-drill.txt new file mode 100644 index 00000000..528a30c1 --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/repoint-drill.txt @@ -0,0 +1,33 @@ +mirror main cde26ab plex: guide checked against 1.43.5.11029-6bf8af3f0 (every HU + EN label read from the new web client, all equal); CHANGELOG + REPORT for the Plex2Shell move + +drill repo main: cde26ab +26: repo_url: file:///opt/docker/felhom-controller/drill-catalog.git +26c26 +< repo_url: https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git +--- +> repo_url: file:///opt/docker/felhom-controller/drill-catalog.git + +Traceback (most recent call last): + File "/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/security-lane-2026-10-23/tools/repoint.py", line 86, in + {"mirror": mirror, "drill": drill, "restore": restore, "status": status}[sys.argv[1]]() + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^ + File "/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/security-lane-2026-10-23/tools/repoint.py", line 74, in drill + restart_sync() + ~~~~~~~~~~~~^^ + File "/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/security-lane-2026-10-23/tools/repoint.py", line 64, in restart_sync + w.sync_rescan() + ~~~~~~~~~~~~~^^ + File "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts/box_walk.py", line 466, in sync_rescan + ctl("POST", "/api/sync") + ~~~^^^^^^^^^^^^^^^^^^^^^ + File "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts/box_walk.py", line 132, in ctl + sess = open(f"{SC}/sess{os.getpid()}.txt").read().strip() + ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +FileNotFoundError: [Errno 2] No such file or directory: '/tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/63b6c84e-0c09-4657-a160-855d2e4fe326/scratchpad/sc9202/sess3574518.txt' +26: repo_url: file:///opt/docker/felhom-controller/drill-catalog.git +27: sync_interval: 15m +-rw------- 1 root root 1994 Oct 22 14:47 /var/lib/docker/volumes/felhom-controller-data/_data/controller.yaml.pre-seclane1023 +mirror main cde26ab plex: guide checked against 1.43.5.11029-6bf8af3f0 (every HU + EN label read from the new web client, all equal); CHANGELOG + REPORT for the Plex2Shell move +cache HEAD cde26ab plex: guide checked against 1.43.5.11029-6bf8af3f0 (every HU + EN label read from the new web client, all equal); CHANGELOG + REPORT for the Plex2Shell move +file:///opt/docker/felhom-controller/drill-catalog.git + diff --git a/documentation/audits/security-lane-2026-10-23/teardown/1-remove-plex.txt b/documentation/audits/security-lane-2026-10-23/teardown/1-remove-plex.txt new file mode 100644 index 00000000..c492b304 --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/teardown/1-remove-plex.txt @@ -0,0 +1,3 @@ +13:25:23 [X] stop -> 200 {'ok': True, 'message': 'Stack plex stop completed'} +13:25:55 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'plex', 'volumes_removed': ['plex_plex_config', 'plex_plex_transcode'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'us +13:26:04 [X] after remove: deployed=False leftovers='/opt/docker/stacks/plex' diff --git a/documentation/audits/security-lane-2026-10-23/teardown/2-repoint-restore.txt b/documentation/audits/security-lane-2026-10-23/teardown/2-repoint-restore.txt new file mode 100644 index 00000000..c7c607a0 --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/teardown/2-repoint-restore.txt @@ -0,0 +1,19 @@ +RESTORED-IDENTICAL +26: repo_url: https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git + +2026/10/23 11:26:11 sync.go:344: [WARN] [sync] git.repo_url changed (cache was cloned from file:///opt/docker/felhom-controller/drill-catalog.git, config says https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git) — re-cloning the catalog cache from the configured repository (R-615) +2026/10/23 11:26:11 sync.go:318: [INFO] [sync] Cloning https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git → /opt/docker/felhom-controller/data/catalog-cache +2026/10/23 11:26:13 sync.go:511: [WARN] [sync] immich: catalog definition REFUSED by the allow-list — the app keeps its last good compose, nothing written: the catalog definition of immich asks for container rights the box does not allow: immich-server: bind /mnt/sizing-bench/appdata/immich -> /usr/src/app/upload rw; immich-server: bind /mnt/sizing-bench/userdata/media/photos -> /external/photos ro +2026/10/23 11:26:13 sync.go:535: [INFO] [sync] Updated plex/docker-compose.yml +2026/10/23 11:26:13 sync.go:535: [INFO] [sync] Updated plex/.felhom.yml +2026/10/23 11:26:13 sync.go:261: [INFO] [sync] catalog moved 17e0da02fd61 -> 389859359491 (0 new, 1 stack dir(s) changed) +2026/10/23 11:26:13 sync.go:299: [INFO] [sync] Catalog sync complete +2026/10/23 11:26:13 sync.go:142: [INFO] [sync] Initial sync: Sablonok frissítve — frissítve: plex + +26: repo_url: https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git +27: sync_interval: 15m +-rw------- 1 root root 1994 Oct 22 14:47 /var/lib/docker/volumes/felhom-controller-data/_data/controller.yaml.pre-seclane1023 +mirror main 17e0da02 Revert "DRILL plex: compose image line back to 1.43.4 ONLY (the ladder untouched) — a fresh install at FROM for the Part 3 proof; reverted right after" +cache HEAD 3898593 D39 = B: the security-fix lane — a `security_fix` ladder step (box walk required, bench optional, CVE/GHSA notice), its gates + 8 decoys, the writer (--write-ladder --security), the box venue (box_walk.py --security, drill-mirror refresh); two stale tests fixed +https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git + diff --git a/documentation/audits/security-lane-2026-10-23/teardown/3-image-0.341.0.txt b/documentation/audits/security-lane-2026-10-23/teardown/3-image-0.341.0.txt new file mode 100644 index 00000000..09e5a0e0 --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/teardown/3-image-0.341.0.txt @@ -0,0 +1,4 @@ +felhom-controller gitea.dooplex.hu/admin/felhom-controller:0.341.0 Up 25 seconds (healthy) +0 +removed-identical-save +34 diff --git a/documentation/audits/security-lane-2026-10-23/tools/chain.py b/documentation/audits/security-lane-2026-10-23/tools/chain.py new file mode 100644 index 00000000..366c8f60 --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/tools/chain.py @@ -0,0 +1,25 @@ +"""chain.py — press "run tonight's chain now" (POST /api/debug/backup/night-chain, R-705) on the box and quote the update +leg's own lines; then read Plex's four observables. D39 = B proof (felhom.eu audits/security-lane-2026-10-23).""" +import json, re, sys, time +sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts") +import box_walk as w +w.LOG = [] +w.login() +w.refresh_box_mirror() +w.sync_rescan("plex", "plexinc/pms-docker:1.43.5.11029-6bf8af3f0", tries=20, delay=6) +print("before:", json.dumps(w.observables("plex"))) +print("badge before:", w.badges("plex")) +since = w.guest("date -u +%Y-%m-%dT%H:%M:%SZ").strip() +code, d = w.ctl("POST", "/api/debug/backup/night-chain") +print("night chain ->", code, str(d)[:200]) +leg = "" +for _ in range(240): + time.sleep(10) + leg = w.guest("docker logs --since %s felhom-controller 2>&1 | grep -E 'update-leg' | grep -v DEBUG | cut -c1-400" % since) + if re.search(r"update leg .*: done=", leg): + break +print("the leg's own lines:\n" + leg) +time.sleep(10) +print("after:", json.dumps(w.observables("plex"))) +st = w.stack("plex") +print("state", st.get("state"), "hold", st.get("hold_reason"), "phase", st.get("update_phase")) diff --git a/documentation/audits/security-lane-2026-10-23/tools/repoint.py b/documentation/audits/security-lane-2026-10-23/tools/repoint.py new file mode 100644 index 00000000..d7e98de6 --- /dev/null +++ b/documentation/audits/security-lane-2026-10-23/tools/repoint.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +"""repoint.py mirror|drill|restore|status — point a box (scratch 9202; copied from audits/burndown-3-2026-10-17/tools/ for the D39 security lane, 2026-10-23) at the drill catalog WITHOUT a credential (R-992). + +WHY A LOCAL MIRROR. `09` §6.5's route needs a catalog credential on the box: the drill repo is private (R-992). This +session could not mint a READ-ONLY one — the Gitea credential on DooPlex is itself a token, and Gitea answers 403 to a +token asking to list or create tokens (measured 2026-10-14) — and the night of 2026-10-06 put the admin WRITE token +into the box's controller.yaml, which this task forbids. The cache-staging workaround of 2026-10-12 does not hold for a +test that lasts days: every sync (and every controller start) re-applies the live catalog over the staged templates +and over the installed apps' compose files (Syncer.copyTemplates, no deployed check). + +So the drill catalog is MIRRORED onto the box: a bare git repository inside the controller's own data volume, and +`git.repo_url: file:///opt/docker/felhom-controller/drill-catalog.git`. The controller's sync runs exactly as for a +remote (clone/fetch/reset, the R-615 re-clone on a changed URL); no secret is on the box. DooPlex updates the mirror +from the drill repo with a git bundle (`mirror`). The drill repo on Gitea stays the record. + + SC=~/.felhom-tester1 TARGET=tester-1 python3 repoint.py mirror # (re)load the mirror from the drill repo main + ... repoint.py drill # save controller.yaml (once), set repo_url to the mirror, restart, sync, rescan + ... repoint.py restore # put the saved controller.yaml back (byte-identical), restart, sync, rescan + ... repoint.py status +""" +import os, subprocess, sys, time +sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts") +import box_walk as w + +DRILL = "/mnt/5_hdd/felhom.eu/drill/app-catalog-drill" +VOL = "/var/lib/docker/volumes/felhom-controller-data/_data" # guest path of the controller's data volume +CVOL = "/opt/docker/felhom-controller" # the same, inside the controller container +SAVE = f"{VOL}/controller.yaml.pre-seclane1023" +MIRROR_URL = f"file://{CVOL}/drill-catalog.git" + + +def status(): + print(w.guest(f"""grep -nE 'repo_url|sync_interval' {VOL}/controller.yaml; ls -l {SAVE} 2>&1 | tail -1 +docker exec felhom-controller sh -c 'git -C {CVOL}/drill-catalog.git log -1 --format="mirror main %h %s" main 2>&1; + git -C {CVOL}/data/catalog-cache log -1 --format="cache HEAD %h %s" 2>&1; git -C {CVOL}/data/catalog-cache remote get-url origin'""")) + + +def mirror(): + b = "/tmp/drill-seclane1023.bundle" + subprocess.run(["git", "-C", DRILL, "bundle", "create", b, "main"], check=True, capture_output=True) + head = subprocess.run(["git", "-C", DRILL, "rev-parse", "--short", "main"], capture_output=True, text=True).stdout.strip() + ssh = w.ssh_args() + # bundle -> PVE host -> guest -> controller volume; the bare repo is made and fetched BY the controller's own user + subprocess.run(["scp", "-q", "-o", "StrictHostKeyChecking=accept-new"] + (["-J", w._T["jump"]] if w._T["jump"] else []) + + [b, f"{w._T['ssh']}:{b}"], check=True) + subprocess.run(ssh + [f"pct push {w.GUEST} {b} {VOL}/drill-seclane1023.bundle && rm -f {b}"], check=True) + print(w.guest(f"""set -e +docker exec felhom-controller sh -c 'test -d {CVOL}/drill-catalog.git || git init -q --bare -b main {CVOL}/drill-catalog.git +git -C {CVOL}/drill-catalog.git fetch -q {CVOL}/drill-seclane1023.bundle +main:main && rm -f {CVOL}/drill-seclane1023.bundle +git -C {CVOL}/drill-catalog.git log -1 --format="mirror main %h %s" main'""")) + print("drill repo main:", head) + os.remove(b) + + +def restart_sync(): + w.guest("docker restart felhom-controller >/dev/null") + for _ in range(60): + time.sleep(3) + try: + w.login() + break + except SystemExit: + continue + w.sync_rescan() + time.sleep(3) + print(w.guest("docker logs --since 3m felhom-controller 2>&1 | grep -E '\\[sync\\]' | grep -v DEBUG | tail -8")) + + +def drill(): + print(w.guest(f"""set -e +test -f {SAVE} || cp -p {VOL}/controller.yaml {SAVE} +sed -i -E 's#^(\\s+repo_url: ).*$#\\1{MIRROR_URL}#' {VOL}/controller.yaml +grep -n 'repo_url' {VOL}/controller.yaml; diff {SAVE} {VOL}/controller.yaml || true""")) + restart_sync() + status() + + +def restore(): + print(w.guest(f"""set -e +cp -p {SAVE} {VOL}/controller.yaml +cmp {SAVE} {VOL}/controller.yaml && echo RESTORED-IDENTICAL; grep -n repo_url {VOL}/controller.yaml""")) + restart_sync() + status() + + +{"mirror": mirror, "drill": drill, "restore": restore, "status": status}[sys.argv[1]]() diff --git a/documentation/backlog/CLOSED-ITEMS.md b/documentation/backlog/CLOSED-ITEMS.md index c0afa505..7c068580 100644 --- a/documentation/backlog/CLOSED-ITEMS.md +++ b/documentation/backlog/CLOSED-ITEMS.md @@ -26,6 +26,12 @@ --- +## 2026-10-23 day (later) — D39 = B: the security-fix lane + +| Row | What | Closed | Evidence | +|---|---|---|---| +| **R-1239** | **The catalog has no fast lane for an urgent security fix — a decision for the operator.** (P2) | CLOSED 2026-10-23 — **D39 = B** (operator ruling by starting the D39 brief; `09` §3 decision 255): a `security_fix` ladder step, box walk required, bench optional, a CVE/GHSA notice as the authority, no operator word per incident. Catalog `3898593` (format, gates, 8 decoys, writer `--write-ladder --security`, `box_walk.py --security`); controller **0.341.0** (the leg takes it; global floor 11:24 UTC, MinAgent 0.158.1). **Reasoning kept:** a security-fix step never crosses a database engine major and is never a same-tag fix (decision 52's lane); a controller ≤ 0.340.0 skips it at night and a hand press takes it. | `audits/security-lane-2026-10-23/` (negative control `failed` and refused; Plex 1.43.4 → 1.43.5 proven on 9202; 0.340.0 night `not_proven` + hand press; rc.1 night `security fix … taken`) | + ## 2026-10-23 day — the releases and the DooPlex hour (the operator's „go") | Row | What | Closed | Evidence | diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index 62a6d4b3..5da9e03b 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -143,12 +143,11 @@ stopping line that lies. | **R-1215** | Apps & catalog | P4 | **One Immich photo upload hung 10 minutes with no answer and was not stored, while the box was healthy.** Night soak 2026-10-21 (`audits/night-2026-10-21/P1-soak/08-load/proof.log`): cycle 36, `IMG_20120021.jpg` sent 19:38:30 UTC, the uploader gave up at its 600 s read limit; Immich's database holds 302 photos for that user, the other cycles 303 (`P1-soak/09-immich-cycle36.txt`). At that moment memory „full" pressure was 3.9 % and `/api/health` answered in 0.26 s. 1 of about 26 000 uploads that night. Not diagnosed: Immich restarted at 02:16 for the off-site dump, so its log of 19:38 is gone. The Immich phone app retries a failed upload. | WATCHING — **2026-10-22 night:** not reproducible tonight; the Immich server log of 19:38 is gone (Immich restarted at 02:16 for the off-site dump). What the household sees: the upload fails after 10 minutes and the Immich phone app sends it again. | — | Watch the next long upload run with the Immich server log saved; file upstream only with a log | CC | -## App updates — 6 rows (P2 2, P3 4) +## App updates — 5 rows (P2 1, P3 4) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| -| **R-1238** | App updates | P2 | **Plex has no test record and no upgrade fixture, so the night leg never updates Plex on a household box.** Filed 2026-10-23. Catalog `6cc317d` moved Plex 1.41.4.9463 → 1.43.5.11029-6bf8af3f0 (the Plex2Shell fix) on the operator's ruling, with NO `update_ladder` entry (the move gate refused; pushed `--no-verify`, the one time). With no ladder, a person's Update press pins the current compose (`stacks/ladder.go` `nextLadderStep`), but the night leg skips the app (`unattended.go` → `LegSkipNoTestRecord`), so every future Plex fix reaches a household only by a hand press. `scripts/upgrade_fixtures.py` has no Plex fixture. A likely seed route is NOT measured: an UNCLAIMED Plex answers a private-address request with no token (`architecture/13-threat-model.md`, the R-1045 row), so the bench may create and read back a library section through Plex's own API without a plex.tv account. **Needs a spike:** prove that route on the bench (seed a section, swap the image, read it back), add the fixture, then `upgrade-test.py --write-ladder` for the current pin — the box walk on 9202 needs the same route behind the setup gate. Evidence: `audits/plex-demo-boxes-2026-10-23/`. | **OPEN** | a claimed-Plex seed route on the bench | the spike above | CC | -| **R-1239** | App updates | P2 | **The catalog has no fast lane for an urgent security fix — a decision for the operator** (decision 13 is a choice, not a defect, R-370). Filed 2026-10-23. Today the only way to move an image without a proven test record is `git push --no-verify` past `check-test-record-move.py`, as catalog `6cc317d` (Plex2Shell) did on the operator's 10:56 ruling — and such a move never reaches a box at night (R-1238). **Options:** (a) keep decision 13 as it is; an urgent fix is a one-time operator ruling + `--no-verify` + a hand press per box — costs an operator word and a CC session per incident, and households wait for the press; (b) a marked `security_fix` ladder entry (the operator's ruling cited, bench run optional, box walk on one test box required) that the gate accepts and the night leg presses — costs one gate rule, one leg rule, decoys, and a tested step that skipped the bench. **CC's pick: (b)**, because (a) does not scale past the first customers and needs the operator awake. | **WAITING-ON-OPERATOR** | the operator's choice | rule on (a) or (b); then CC builds (b) if chosen | operator | +| **R-1238** | App updates | P2 | **Plex has no test record and no upgrade fixture, so the night leg never updates Plex on a household box.** Filed 2026-10-23. Catalog `6cc317d` moved Plex 1.41.4.9463 → 1.43.5.11029-6bf8af3f0 (the Plex2Shell fix) on the operator's ruling, with NO `update_ladder` entry (the move gate refused; pushed `--no-verify`, the one time). With no ladder, a person's Update press pins the current compose (`stacks/ladder.go` `nextLadderStep`), but the night leg skips the app (`unattended.go` → `LegSkipNoTestRecord`), so every future Plex fix reaches a household only by a hand press. `scripts/upgrade_fixtures.py` has no Plex fixture. A likely seed route is NOT measured: an UNCLAIMED Plex answers a private-address request with no token (`architecture/13-threat-model.md`, the R-1045 row), so the bench may create and read back a library section through Plex's own API without a plex.tv account. **Needs a spike:** prove that route on the bench (seed a section, swap the image, read it back), add the fixture, then `upgrade-test.py --write-ladder` for the current pin — the box walk on 9202 needs the same route behind the setup gate. Evidence: `audits/plex-demo-boxes-2026-10-23/`. **2026-10-23 (later): the NEXT Plex fix need not wait for this** — the security-fix lane (`09` §3 decision 255, D39 = B, controller 0.341.0) takes a box-only step with `seed: "none"` at night (proven on 9202 with Plex 1.43.4 → 1.43.5, `audits/security-lane-2026-10-23/`). The bench fixture stays the aim: it is the only check that reads Plex's data back after a step. | **OPEN** | a claimed-Plex seed route on the bench | the spike above | CC | | **R-440** | App updates | P3 | **[P2-MEDIUM] 23 catalog image pins float, so an update is not reproducible.** `compose pull` on a moving tag fetches whatever upstream published that day. **MEASURED 2026-09-01 over `app-catalog-felhom.eu` @ `29edad9c5bf4`: 79 `image:` lines across 53 apps, 66 distinct; 23 of those lines carry a tag with no patch version.** `postgres:16-alpine` (8 apps), `redis:7-alpine` (6), `mariadb:11.6` (2), plus one each of `postgres:15-alpine`, `postgis/postgis:16-3.5-alpine`, `mariadb:11.4`, `mariadb:12.3`, `ghcr.io/claperco/claper:2.5`, `ghcr.io/thomiceli/opengist:1.13`, `wger/server:2.6`. **A 24th is arguable and is recorded rather than rounded away:** `ghcr.io/immich-app/postgres:16-vectorchord0.4.3-pgvectors0.2.0` pins both extensions exactly but leaves the PostgreSQL patch floating. A customer pressing Frissites can therefore swap their DATABASE ENGINE build with no catalog change and no record; two boxes updated on two days end up different. **Severity MEDIUM on its own; it becomes BLOCKING the moment a pre-update copy exists**, because "what did we upgrade from and to" must be recordable and today it is not — which is also why R-440 must be read next to the digest discipline in Rule 10 of the spike. **MEASURED LIVE 2026-09-01 — the floating pins have ALREADY moved, with a passing control.** Running digests on demo-hp compared against what the registry serves for the same tag today: **`mariadb:11.4` MOVED** (`sha256:4f1d8d20...` -> `sha256:611a2fcc...`) and **`mariadb:12.3` MOVED** (`sha256:a02fe89c...` -> `sha256:dd9b303a...`), while `postgres:16-alpine`, `redis:7-alpine`, `mariadb:11.6` and `opengist:1.13` were SAME — **and both fully-pinned CONTROLS (`rommapp/romm:5.0.0`, `privatebin/pdo:2.0.5`) were SAME.** So on a box with ZERO visible drift by tag, pressing Frissites today silently swaps the DATABASE ENGINE build under `romm` and `bookstack`, with no catalog change and no record. **Compounding fact found while reading:** the recovery unit records `ImagePins` but the manifest comment says *"image NOT stored - re-pulled on restore"*, so a RESTORE of a floating-pinned app also re-pulls whatever is current — the same non-reproducibility on the recovery path. **HALF OF THE ANSWER SHIPPED 2026-09-02 (controller v0.233.0, slice 1): `app.yaml.installed_images` now records, per compose SERVICE, the reference AND the repo digest each container was actually created from — so "what did we upgrade FROM" is answerable on any box that has taken one lifecycle action since the upgrade.** What is still missing is the other half: comparing that digest against what the registry serves for the same tag TODAY, which needs a network call the render path deliberately does not make (see R-446). **The row therefore stays OPEN and its rank is unchanged** — recording a digest does not make a floating pin reproducible; it makes the drift measurable after the fact. `audits/SPIKE-app-update-2026-09-01.md` **— NIGHT 2026-09-23:** every image moved tonight (and the 21 backfilled moves) carries its resolved digest in the catalog's test record; the floating pins themselves still float until the box pulls by digest (`09` §6.4 part 6). **-- MEASURED 2026-09-30 on demo-hp 9201: a fresh install or a guarded Update renders `name:tag@sha256` from the ladder (`digest.go` `RenderWithLadderDigests`) — 9 of 20 services there run such a definition (adventurelog, docmost, paperless-ngx). The other 11 run TAG-ONLY definitions: bookstack, kimai, opengist, privatebin, romm were installed before v0.269.0 and not updated since, and `CarryDigests` (`digest.go:141`) keeps only a digest the running definition already has; bentopdf and calibre-web have no ladder at all. So a re-pull of those (a restore) takes whatever the tag serves that day. `audits/pg-last-six-2026-09-30/E/E2-digests-demo-hp.txt`.** **-- 2026-09-30 (evening):** calibre-web now has a ladder entry (`53a4a1d`), so a new install or a guarded Update renders its tested digest; bentopdf still has none. `audits/more-night-apps-2026-09-30/` **Merged 2026-10-05 from R-446 (duplicate):** the household-visible consequence — for these apps the „Naprakész" badge can never turn „behind" (`felhom-controller controller/internal/stacks/updateorder.go:96` returns false with no catalog digests / no test date); R-740 (same-tag re-test) is closed, so floating-tag drift for laddered apps is handled. | **NARROWED 2026-09-30 — floating only for (1) apps with no ladder entry and (2) apps installed before v0.269.0 until their next guarded Update; owner: CC.** **Re-ranked 2026-10-03: P2→P3: narrowed to apps with no ladder entry and pre-v0.269 installs; each guarded update fixes the latter.** | — | — | CC | | **R-462** | App updates | P3 | **[P2-MEDIUM] Widen the upgrade harness beyond three apps — and the cost is dominated by FIXTURES, not by machine time.** The R-449 harness works and is proven by a red negative control (`audits/SPIKE-upgrade-test-2026-09-06.md` §1). **Costed with this run's REAL numbers rather than an estimate:** a successful edge takes **6.4 s – 305.1 s, median 71.8 s**; a FAILING edge takes **556 s**, roughly **8×**, because a negative is only honest if it waits out the full settle window; 3 apps / 11 images cost **5.07 GB**, so 53 apps naively extrapolate to **~90 GB** and, at the median, about an hour of harness time for one edge each. **THAT EXTRAPOLATION UNDERSTATES THE REAL COST BY AN ORDER OF MAGNITUDE, and that is the point of this row.** Two of the three apps needed a bespoke non-browser seed route; one needed two attempts and a discarded approach; one (bookstack) can only ever be half-proven (R-460). **Fixture time scales with apps and does not amortise.** **The decision this row is really asking for is scope, not schedule:** all 53, or only the apps a customer would lose data from, or only apps whose catalog transition is a MAJOR. **Recommended shape, NOT a design — the operator picks:** start with the apps that carry a database, because §3 measured that the abort question only ever bites there. Owner: **VIKTOR rules on scope, CC implements.** `audits/SPIKE-upgrade-test-2026-09-06.md` §5 **ROW CORRECTED 2026-09-21: the scope is NOT open and this row said it was.** It read *"VIKTOR rules on scope"*; the operator ruled on 2026-09-13 (`09` §3 decision 6) that the upgrade test goes to **ALL** apps through the nightly rotation, explicitly *not* "database apps first". What is open is the WORK, not the scope. An ORDER inside that ruling — the 15 database services first, because that is where a wrong answer costs data rather than uptime — is costed as a drill brief in `09` §6.4: legs A–E ≈ **21–34 CC-hours** plus ~25–30 GB on a scratch host, with legs C (a PostgreSQL `pg_upgrade` rehearsal) and E (one automatic night on a throwaway) the two that unblock a decision. **-- UPDATE NIGHT 2026-09-21:** **The count moved from 3 apps to 21 EDGES ACROSS 19 APPS.** The update night walked real within-a-major upstream edges on scratch guest 9202 through the product's own guarded Update, each app seeded and read back through its OWN front door with a negative control on every readback: **14 proven, 3 failed, 4 inconclusive**. Proven: actualbudget, audiobookshelf, bookstack, docmost, grafana, home-assistant, mealie, n8n, navidrome, papra, privatebin, romm, vikunja, and nextcloud's MariaDB engine major. **Ten of the fourteen printed a verbatim migration line**, so the database really was rewritten and the data still read back. Box-side fixtures for 20 apps now exist at `audits/update-night-2026-09-21/fixtures.py`, and four (actualbudget, navidrome, audiobookshelf, vikunja) are ported into `app-catalog-felhom.eu/scripts/upgrade_fixtures.py` with seven new EDGES (U1-U7) so the same edges can be run on the harness venue **with their ABORT step**, which the box deliberately does not offer. **OWED, stated so it is not mistaken for done:** the U1-U7 harness RUNS (the code is in, the runs are not), and fixtures for the four inconclusive apps, of which two (vaultwarden, zipline) cannot be seeded at all while the catalog rightly closes their sign-up (see R-624). **-- 2026-09-23, the RomM lesson is IN THE HARNESS:** `upgrade-test.py` v2 runs every edge that read back under light load for `--soak` seconds (default 600) and reads the kernel's `oom_kill` counter host-side; a kill or restart turns `proven` into `failed`, a peak over 80 % of the limit adds `memory_tight`. **Red-proof:** romm 5.0.0 → 5.3.0 on the template AS PROMOTED (512M, four workers) — seeded, migrated, read back, and then **OOM-killed at +76 s** under four light callers, verdict `failed` (Docker's own OOMKilled read true here; restarts stayed 0, which is why the walk never saw it). Ten minutes is ample for this failure; demo-hp's first kill came at two hours only because nothing was loading it. Evidence `audits/update-rulings-2026-09-23/harness/`. **— NIGHT 2026-09-23:** the bench and the box now share ONE fixture set — the box walk's fixtures run on the bench through `upgrade_boxport.py` (ported verbatim into the catalog), plus a new wishlist fixture and fixes for opengist 1.15, komga (`/api/v2/users/me`) and nextcloud (wait for `occ status`). 14 apps / 15 edges tried; 12 proven on both venues and published with their test records (`audits/DRILL-night-2026-09-23.md` Part C). **-- 2026-09-30 (day brief): three new front-door fixtures (sparkyfitness, rallly, outline; catalog `e6f3ec2`) and five apps moved on both venues — the three PostgreSQL conversions (R-463) plus bookstack 26.05.5 → 26.09.1 and kimai apache-2.57.0 → 2.67.0 (upstream dropped the `apache-` prefix; the plain tag's digest equals `apache`'s, measured). The catalog currency and the next-apps ranking: `audits/catalog-currency-2026-09-30.md` — 25 of 53 apps behind upstream inside a major, 19 across one (morning); after the session **31 apps can update themselves at night (+ nextcloud with a whole copy)**, 32 carry a proven step, 21 have no ladder.** **-- 2026-09-30 (evening, more-night-apps brief): ten more steps published on both venues** — first ladders for calibre-web, gitea, wger, crafty-controller, uptime-kuma, zipline (new front-door fixtures; gitea's installer form), within-major steps for emby, ghost, home-assistant, outline, rallly. **Night-updatable by the audit's method (a): 30 + 2 conditional at the session's start → 35 + 3 conditional** (calibre-web conditional: `files_may_change`). zipline got a two-step ladder 4.6.1 → 4.7.0 → 4.8.0 (the direct jump fails, R-742). Stopped: wanderer (the bench cannot run it, R-739). Fixture/harness defects fixed on the way: R-735, gitea's installer, the HTTPS backend on the bench, per-file names for `files_may_change`. `audits/more-night-apps-2026-09-30/` | **READY — rank P2-MEDIUM; owner: CC (scope already ruled, `09` §3 decision 6)** **Re-ranked 2026-10-03: P2→P3: 35+ apps already update by proven steps; an app without a ladder simply does not move, which is safe.** **NARROWED 2026-10-20 (night brief part 2, Part 5):** the fixture side is DONE for every app installed on a test box — 58 box fixtures run on both venues; of the 16 installed apps only bentopdf has none (it keeps no data). Upstream within a major (check-currency, registries read): privatebin 2.0.6→2.1.0, home-assistant 2026.9.4→2026.10.2, romm 5.3.1→5.4.0, plex 1.41.4→1.43.4, kimai 2.67.0→2.70.0 — the list left. **No edge was run:** the bench (guest 9401, stopped) and the drill catalog the box venue needs were outside the brief's machines and repos; the operator's word for both is on STATUS's morning list (`audits/night2-2026-10-20/P5/r462-list.txt`). | — | — | CC | | **R-683** | App updates | P3 | **[P3-LOW] Watch: after a power cut during an update's health check, the hold named an HOUR-OLD second-drive copy, not the one the update's own backup should have just made.** 2026-09-24 chaos round 3 (nextcloud, `backup_max_age: 1m`): no `backing-up` phase was seen and the hold named Tier 2 at 13:04 for an update pressed at 14:04; the pre-cut controller log was lost with the container (the runner now saves it at arm time — R-320). Round 11, the same action without a power cut, named a fresh 14:34 copy and logged the Tier-2 copy. The sentence was TRUE (it named the copy it offered); the question is why the update did not back up first. Not reproduced; watch the next power-cut drill. `audits/night-2026-09-24/E/round-03*.json`, `E/round-11-controller-pre.log` | **OPEN — P3; owner: CC (watch)** | — | — | CC | @@ -322,11 +321,12 @@ stopping line that lies. | **R-990** | Business & legal | P4 | **Operator: read `documentation/audits/forum-research-2026-10-12/FELHOM.md` §5 and rule on each recommendation.** READ-ONLY research 2026-10-12: three years (#7943–#11092, 2,908 posts, 195 people, 82 beginner threads) of the public PROHARDVER! topic „Home server / házi szerver építése”, tagged and counted by person (`FINDINGS.md`, method and spot-check in `METHOD.md`; no username or post text in git). Ten ranked recommendations, none acted on; the top three: a „which machine works” page (8 of 33 beginners who name their hardware own only a Synology, a router disk or a Raspberry Pi — the installer cannot run there; the same gap is R-989's first recommendation from a different source, and the shipped-page half is R-991), „you do not choose an operating system — we do” (the biggest beginner question, 20 of 82 threads), and „bring your old office PC” as the cheap entry. Catalog gap (≥ 3 people): a DLNA server (7), an ad/DNS blocker (5), a camera recorder (5). | **WAITING-ON-OPERATOR** — filed 2026-10-12 | the operator's reading | Operator: rule on each of the ten (yes / no / later). If nothing is done: nothing changes; the research ages | operator | | **R-1035** | Business & legal | P4 | **Did the „felhő tárhely” page earn anything? Two dated checks.** **Baseline (reported by the operator from Search Console, 2026-10-14, last 3 months):** 1 click, 48 impressions, CTR 2.1 %, average position **73.6**; „felhő tárhely” 19 impressions, „felhőtárhely” 5, „felhő alapú tárhely” 3. CC measured separately that the phrase „felhő tárhely” appeared **0 times** on the home, FAQ and backup pages — Google was showing the home page for those queries by accident. `/sajat-felho-tarhely` + `/en/own-cloud-storage` published **2026-10-14**; full baseline, price sources and the power provenance in `audits/seo-2026-10-14/BASELINE.md`. **What to measure, both times:** impressions and average position for „felhő tárhely” and „saját felhő tárhely”, plus the new page's own impressions, clicks and position. **A null result at +4 weeks means nothing on its own** — a new page on a site at position 73.6 takes weeks to months to move, and „felhő tárhely” alone is held by Microsoft, Google and the big list sites; the page aims at the two longer phrases. That is why there are two checks and not one, and why neither should be read as a verdict on the copy. **There is no Search Console connector in this project, so CC cannot read these numbers** — the operator reads them, CC records and compares. **Blocked until the operator asks for indexing** (Search Console → URL-ellenőrzés → the URL → „Indexelés kérése”): until that happens, or Google finds the page itself, both checks read zero for a reason that is not the page. **This is a slow channel and does not replace talking to people.** | **WATCHING** | operator: request indexing, then supply the Search Console read | 2026-11-11: first read (+4 weeks); 2026-12-09: second (+8 weeks), compared with the first | operator + CC | -## Process & tooling — 31 rows (P2 1, P3 8, P4 22) +## Process & tooling — 32 rows (P2 1, P3 8, P4 23) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| | **R-1083** | Process & tooling | P3 | **The scratch box 9202 cannot use any feature that needs the agent: its `bootstrap.json` has no `local_api` block, so its controller never gets an agent client.** MEASURED 2026-10-15 (storage-identity build A): 9202 put on controller 0.317.0 by hand to show that a NAS share no longer hides the one-copy notice (R-1075); its `bootstrap.json` (read on the box) holds no `local_api`, so the controller has no agent to ask and keeps the old `st_dev` rule — the check could not tell old from new — 9202 was put back on 0.314.0. Every agent-coupled feature (drives, same-disk, format, network storage) is untestable on the scratch box today, and the NAS leg of build A is proven by a red-proofed unit test only. Evidence: `audits/storage-identity-buildA-2026-10-15/9202-upgrade.txt`. | **OPEN — the exact gap, read 2026-10-19 (no agent change tonight):** the agent mints a per-guest local-API token in ONE place, `--selftest=provision`'s back half (`cmd/felhom-agent/main.go` `bh.Provision` after `engine.RunBringUp`), which only runs for a guest it brings up from a golden archive in the same call. There is no product way to mint for an EXISTING guest (9202) — no subcommand, no local-API route; the token store `/var/lib/felhom-agent/local-tokens.log` is written only by that path. Options: an agent subcommand `--mint-guest-token ` (owner-only, writes bootstrap's `local_api`), or re-provision 9202 through `selftest=provision` (destroys and rebuilds the scratch guest — not done; rule 6). Until then 9202 is controller-only. OPEN **2026-10-20 — left out of agent 0.161.0 on purpose:** the code would be small (a root-only subcommand that mints the token and adds `local_api` to an existing guest's `bootstrap.json`), but an agent key makes 9202's controller book the agent's whole-guest backups, one tier of which writes to ep0 (the demo-hp namespace) — and 9202 is outside the `felhom` pool. Needs, before it ships: a way to give a scratch guest a key with NO whole-guest tiers (or the operator's word that 9202 may write to ep0), and 9202 in the pool. | — | Give 9202 a local-API token the normal way (the agent mints it for its guest), or record in `runbooks/target-selection.md` that 9202 is controller-only; then show the NAS notice live. | CC | +| **R-1240** | Process & tooling | P4 | **Scratch guest 9202 holds 34 saved copies of its `controller.yaml`, each carrying the catalog's git token** (`controller.yaml.pre-`, 2026-09-21 → 2026-10-17, root 0600, inside the guest's controller volume). Every drill session that repointed 9202 saved one and none removed it after its byte-identical restore. Found 2026-10-23 (the D39 session removed its own after `cmp` proved it identical). Not read: whether the token in them is still live (R-925 rotated tokens). | **OPEN** | — | read which token the copies carry (by hash, never printed); if the live one, delete the copies; add „remove the save after an identical restore" to `repoint.py restore` | CC | | **R-1081** | Process & tooling | P4 | **Two published controller tags hold content that is not what their number says: `0.315.0` and `0.316.3`.** Both were caused by CC on 2026-10-15, and both by the same shape — a guard that was not on the path the work takes. **`0.316.3`:** the build directory's `build.sh` is a symlink into the SHARED clone's working copy, which predated the `FELHOM_REPO_DIR` override, so the override was silently ignored and the build copied that tree — which held 31 uncommitted files of another session's physical-disk storage work. The banner printed `Commit: 0225255` with no `Repo:` line. **Nobody deployed it; that was luck, not a control.** Fixed at the root in controller v0.316.4: `build.sh` now refuses `--push` from a tree that is dirty, not at `origin/main`, or not a git checkout, with four test cases red-proved. **Neither tag is re-pushed or deleted** — a version number is pushed once, and that applies to numbers CC spoiled. **No box runs either:** demo-hp 9201 is on 0.317.0, the floor is 0.314.0, and the button offers the highest tag. **`0.315.0`:** Caused by CC on 2026-10-15 while proving R-1080's own fix: the guard was added to `controller/build.sh` in the repo, but the documented release command runs `$FELHOM_ROOT/build/felhom-controller/build.sh`, which was a **separate copy taken 2026-07-18** and had no guard. Running the refusal against a real existing tag therefore rebuilt and re-pushed 0.315.0 — a fourth time — with v0.316.0's code under it (digest `8887343011d6` → `b809201841f7`). **Fixed at the root in the same session:** the build directory's script is now a symlink to the repo's, the old copy kept beside it as `build.sh.stale-copy-2026-07-18`, and the refusal was then proven to fire from the documented path. **No box is affected:** nothing runs 0.315.0 (demo-hp 9201 is on 0.316.2), the floor is 0.314.0, and the household button offers the highest tag. What remains is two tags that must never be deployed and are no longer reproducible from the registry. | **OPEN** — filed 2026-10-15; nothing to do unless someone needs one of those two images, and then the answer is the current release | — | Leave them. If an audit ever needs the real 0.315.0 or 0.316.3, build it from the matching commit under a NEW number and say so | CC | | **R-1034** | Process & tooling | P3 | **Gate 17 checks the FAQ JSON-LD on two pages only, by a hard-coded list — a FAQPage anywhere else is unchecked.** `scripts/site_gates.py` gate 17 loops `for p in ("gyik.html", os.path.join("en", "faq.html"))`, so its „JSON-LD equals the visible questions and answers” guarantee stops at those two files. **Since 2026-10-14 two more pages carry a FAQPage block the gate never reads:** `/sajat-felho-tarhely` and `/en/own-cloud-storage`. Their JSON-LD was *derived* from the visible headings and paragraphs by a script and then verified verbatim against the rendered text, so it is correct today — but nothing stops the next copy edit moving the visible text and leaving the structured data behind, which is exactly the failure gate 17 exists to catch, and the one Google penalises. **Why it is not a one-line fix:** gate 17's extractor is written for the FAQ pages' accordion markup (`