hub-safety session: R-135/R-133/R-604/R-530/R-508/R-509/R-880 closed, R-861/R-173/R-518/R-519 narrowed, R-879/R-881 opened (336 → 332); 03 §3.1, 05 §16, golden 0.296.0, the hub-DB off-site plan, STATUS
gates / gates (push) Successful in 32s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-05 13:47:57 +02:00
parent 2b30733b0d
commit 0826e41b31
44 changed files with 1511 additions and 31 deletions
@@ -0,0 +1,8 @@
== Part A live, hub 0.135.0, 2026-10-05T09:15:34Z, ClusterIP, Basic auth (password from the credentials file, not printed)
POST /configuration/global-floor, Basic, NO header, Origin evil (empty form): 403
POST /no-such-route, Basic, NO header: 403
POST /no-such-route, Basic + X-Felhom-Operator: cli (passes the gate → router 404): 404
POST /no-such-route, header but NO credentials: 401
GET /system, Basic, no header: 200
2026/10/05 11:15:34 [WARN] CSRF rejected: POST /configuration/global-floor from 10.42.0.1:36344
2026/10/05 11:15:34 [WARN] CSRF rejected: POST /no-such-route from 10.42.0.1:41323
@@ -0,0 +1,50 @@
# Hub state-changing routes and how each is protected (hub v0.135.0, R-135)
Every route below is reached through `RequireAuth` → `ServeHTTP`; the CSRF gate is the first thing `ServeHTTP` does for any
method other than GET/HEAD/OPTIONS, before the route switch — so the protection is the same for every route, and an
unknown path is refused by the gate before it can 404.
| Route (representative path) | Protected how | Test |
|---|---|---|
| `POST /configuration` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /apps/demo/reset-telemetry` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /apps/demo/dismiss-issues` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /offsite/endpoints` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /offsite/endpoints/1/delete` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /appliances/1/bind` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /appliances/1/discard` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /hosts/h1/delete` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /hosts/h1/reveal-recovery-credential` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /hosts/h1/request-logs` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /customers/c1/block` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /customers/c1/selfbind-link` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /customers/c1/unblock` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /customers/c1/geo/disable` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /customers/c1/floor` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /customers/c1/create-config` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /customers/c1/request-log-tail` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /configs/new` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /configuration/global-floor` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /configuration/artifacts` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /configuration/password` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /configs/c1/delete` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /configs/c1/edit` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /configs/c1/offsite-reissue` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /configs/c1/claim-resend` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /configs/c1/pbsdr-reissue` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /configs/c1/offsite-freeze` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /configs/c1/regen-password` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /configs/c1/reset` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /offsite/remove-unpinned/c1` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /offsite/abandon-cancel/c1` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /offsite/window-grant/c1` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /offsite/windows-enabled` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /offsite/key-audit` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /os/ring/h1` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /os/enabled/h1` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /os/approve-now` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /os/approve-docker` | session cookie + token, OR Basic auth + `X-Felhom-Operator` | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /no-such-route` | the gate, before routing (not a route) | `TestR135_BasicAuthWithoutHeaderIsRefused`, `TestR135_SessionWithoutTokenIsRefused`, `TestR135_TheTwoAllowedShapesPassTheGate` |
| `POST /login` | exempt (no session to ride; a wrong password is 401) | — |
| `POST /bind/<token>` | exempt (public self-bind; the e-mailed URL token is the capability, rate-limited) | existing `selfbind_test.go` |
| `GET` routes | not gated by design (a GET must not change state). **Not audited in this session** for a GET that writes — the route switch sends POST-only actions to handlers that check `MethodPost`, but the GET renderers were not read line by line | `TestR135_GetIsNotGated` |
@@ -0,0 +1,6 @@
== Part B live, 2026-10-05T09:16:00Z: live hub.db copied to scratch, only prefix + length selected, copy shredded after
Tester-2-be8404|enc:v1:|87|2026-10-04 16:07:15
demo-felhom-8363b5|enc:v1:|87|2026-07-18 16:30:41
demo-hp-bb76ea|enc:v1:|87|2026-07-21 16:24:27
tester-1-d70be4|enc:v1:|87|2026-10-04 19:40:27
rows NOT sealed: 0
@@ -0,0 +1,6 @@
== Part B live reveal, 2026-10-05T09:16:15Z: POST /hosts/demo-hp-bb76ea/reveal-recovery-credential (Basic + X-Felhom-Operator), body to a 0600 scratch file, shredded after
reveal HTTP 200
username root@pam password length 32 set_at 2026-07-21T16:24:27Z
the revealed password logs in to demo-hp's Proxmox API (POST /api2/json/access/ticket, root@pam): HTTP 200
control, a wrong password: HTTP 401
2026/10/05 11:16:15 [INFO] operator revealed break-glass console credential for host demo-hp-bb76ea (user=root@pam, secret 32 chars)
@@ -0,0 +1,42 @@
== Part C readings on DooPlex, READ ONLY, 2026-10-05T09:17:24Z
-- where the hub database lives
hub-data pvc-486c9809-4672-4b56-b70e-0bf01d0c3628 1Gi longhorn
-rw-r--r-- 1 root root 374534144 Oct 5 11:14 hub.db
-rw-r--r-- 1 root root 32768 Oct 5 11:16 hub.db-shm
-rw-r--r-- 1 root root 313152 Oct 5 11:16 hub.db-wal
973.4M 373.4M 584.1M 39% /data
-- the exclusion label: PVC (git, manifests/hub.yaml:47, commit 868e8465 2026-02-16 'updated hub yaml', no reason given) vs the live Longhorn Volume
PVC label: disabled
Volume label: enabled
-- recurring jobs
backup-daily backup 0 4 * * * 1 [default]
backup-weekly backup 0 5 * * 0 1 [default]
-- backups of the hub volume (Longhorn backupstore)
2026-10-04T03:05:01Z Completed 708837376
2026-10-05T02:06:26Z Completed 713031680
-- backup target
nfs://192.168.0.180:/mnt/5_hdd/backup/longhorn-pvc?nfsOptions=soft,timeo=330,retrans=3 true
-- which disk holds the target
/dev/sda1
/dev/sdb1
-- DooPlex's own backup service
Mon 2026-10-05 11:30:00 CEST 12min Mon 2026-10-05 11:15:00 CEST 2min 25s ago backup-freshness.timer backup-freshness.service
Tue 2026-10-06 03:19:15 CEST 16h Mon 2026-10-05 03:15:21 CEST 8h ago dooplex-backup.timer dooplex-backup.service
Result=success
ExecMainStatus=0
-- what tells anyone when a backup fails
80:export NOTIFY_ON_FAILURE="true"
81:# export NOTIFY_WEBHOOK_URL="https://your-webhook-url"
137: if [ "${NOTIFY_ON_FAILURE}" = "true" ] && [ -n "${NOTIFY_WEBHOOK_URL}" ]; then
140: "${NOTIFY_WEBHOOK_URL}" || true
prometheus rule backup-freshness-alerts.yml MinecraftBackupStale
prometheus rule backup-freshness-alerts.yml BackupFreshnessExporterDead
prometheus rule longhorn-alerts.yml LonghornVolumeSpaceCritical
prometheus rule longhorn-alerts.yml LonghornVolumeSpaceWarning
prometheus rule longhorn-alerts.yml LonghornVolumeDegraded
prometheus rule longhorn-alerts.yml LonghornNodeStoragePressure
(no rule watches a Longhorn BACKUP's success or age, nor dooplex-backup.service; the only backup-freshness rule is MinecraftBackupStale)
-- does anything leave DooPlex for the hub DB? the off-site route that exists today: ep0 PBS reached through felhom-ep0-pbs-tunnel (pull only, ep0 -> DooPlex)
active
/usr/bin/proxmox-backup-client
/usr/bin/sqlite3
@@ -0,0 +1,9 @@
== Part D live: GET /system on hub 0.135.0 (Basic auth); extracted, no tokens
Version floors: Version floors Global controller floor: 0.292.0 · vouched agent: 0.145.0 Customer Own floor Set Global floor moves it? demo-felhom Demo Ügyfél 0.295.0 unknown no — its own floor applies (at or above the global) demo-hp Demo HP 0.295.0 unknown no — its own floor applies (at or above the global) tester-1 Tester 1 0.295.0 unknown no — its own floor applies (at or above the global)
Agent cell Tester-2-be8404: [('c-warn', '0.142.0 → 0.145.0 (since 2026-10-05)', '3 minor releases behind — sign an agent_update for this box')]
Agent cell demo-felhom-8363b5: []
Agent cell demo-hp-bb76ea: []
Agent cell tester-1-d70be4: []
<td title="current (vouched 0.145.0)">0.145.0</td>
<td title="current (vouched 0.145.0)">0.145.0</td>
<td title="current (vouched 0.145.0)">0.145.0</td>
@@ -0,0 +1,28 @@
== R-518 measure, demo-hp guest 9201, controller 0.295.0, 2026-10-05: POST /api/guest-backup/trigger (the button's call) at 09:19:05Z
2026/10/05 09:19:07 backup_handlers.go:349: [INFO] [web] manual whole-guest backup triggered (quiesce loop)
2026/10/05 09:19:07 quiesce.go:427: [INFO] [quiesce] manual backup requested — quiescing now
2026/10/05 09:19:08 quiesce.go:517: [INFO] [quiesce] backup due on 2 tier(s) — quiescing 9 stack(s): [adventurelog bentopdf bookstack calibre-web docmost kimai opengist paperless-ngx privatebin]
2026/10/05 09:19:29 quiesce.go:566: [INFO] [quiesce] tier local: backup job backup-9201-1791191969558324187 started — polling
2026/10/05 09:23:44 quiesce.go:210: [INFO] [quiesce] a backup cycle is already running — skipping this scheduled check
2026/10/05 09:24:09 quiesce.go:643: [INFO] [quiesce] tier local: backup job backup-9201-1791191969558324187 done — next tier may start (app still quiesced)
2026/10/05 09:24:09 quiesce.go:307: [INFO] [quiesce] tier felhom-pbs is BUSY — the agent refused the backup because a concurrent heavy operation holds it. This is contention, NOT a failure: the tier stays due and retries in 15m0s (contended for 0s)
2026/10/05 09:24:09 quiesce.go:504: [INFO] [quiesce] unquiescing (last tier is busy — deferring to a later cycle): restarting 9 stack(s)
-- container StartedAt after the backup (the apps the quiesce stopped):
2026-10-05T09:24:10.019684173Z adventurelog-postgres
2026-10-05T09:24:10.200085281Z adventurelog-frontend
2026-10-05T09:24:15.705236204Z adventurelog
2026-10-05T09:24:16.331987016Z bentopdf
2026-10-05T09:24:16.974996127Z bookstack-db
2026-10-05T09:24:22.669660366Z bookstack
2026-10-05T09:24:23.521923837Z calibre-web
2026-10-05T09:24:24.437773353Z docmost-postgres
2026-10-05T09:24:24.631250114Z docmost-redis
2026-10-05T09:24:35.395095325Z docmost
2026-10-05T09:24:36.934564476Z kimai-db
2026-10-05T09:24:42.753714563Z kimai
2026-10-05T09:24:43.466405175Z opengist
2026-10-05T09:24:44.478064534Z paperless-redis
2026-10-05T09:24:44.688668856Z paperless-postgres
2026-10-05T09:24:54.928089575Z paperless-webserver
2026-10-05T09:24:55.739635651Z privatebin
RESULT: stop began 09:19:08Z (9 stacks quiesced), local tier 09:19:29-09:24:09, PBS tier BUSY (skipped, retried later), last app back 09:24:55Z => longest stop 5 min 47 s, shortest ~5 min 02 s. 21/21 containers running afterwards.
@@ -0,0 +1,15 @@
09:19:20 running=15 phase=idle
09:19:41 running=4 phase=snapshotted
09:20:02 running=4 phase=snapshotted
09:20:23 running=4 phase=snapshotted
09:20:44 running=4 phase=snapshotted
09:21:05 running=4 phase=snapshotted
09:21:26 running=4 phase=snapshotted
09:21:47 running=4 phase=snapshotted
09:22:08 running=4 phase=snapshotted
09:22:29 running=4 phase=snapshotted
09:22:50 running=4 phase=snapshotted
09:23:13 running=4 phase=snapshotted
09:23:34 running=4 phase=snapshotted
09:23:55 running=4 phase=snapshotted
09:24:16 running=6 phase=done
@@ -0,0 +1,39 @@
== RED-PROOF 1 (R-519): runDBDumpsInternal does not call markRunStarted
=== RUN TestRunRecord_TheRealRunIsOnRecordWhileItRuns
run_record_test.go:78: the run was not on record while it ran — a cut here would go unnoticed
--- FAIL: TestRunRecord_TheRealRunIsOnRecordWhileItRuns (0.00s)
FAIL
FAIL gitea.dooplex.hu/admin/felhom-controller/internal/backup 0.007s
FAIL
== RED-PROOF 2 (R-519): the synthesised status says Success: true again
=== RUN TestRunRecord_SynthesisedStatusIsNotOKAfterACut
run_record_test.go:97: after a cut the synthesised status still reads OK: &{LastRun:2026-10-05 11:34:19.454323277 +0200 CEST m=+0.001336319 Results:[{DB:{ContainerName:adventurelog ContainerID: DBType: DBUser: DBName: StackName:adventurelog} FilePath:adventurelog-postgres.sql Size:0 Duration:0s Error:<nil> Validation:{Valid:false TableCount:0 Error: FileSize:0 ModTime:0001-01-01 00:00:00 +0000 UTC UserTableFound:false UserRows:0 LooksEmpty:false}}] Success:true Duration:0s}
--- FAIL: TestRunRecord_SynthesisedStatusIsNotOKAfterACut (0.00s)
FAIL
FAIL gitea.dooplex.hu/admin/felhom-controller/internal/backup 0.007s
FAIL
== RED-PROOF 3 (R-519 wiring): main() does not call loadRunRecordAtStartup
=== RUN TestMainWiresRunRecord
run_record_wiring_test.go:47: main() never calls loadRunRecordAtStartup — a cut run is never said on the page
--- FAIL: TestMainWiresRunRecord (0.01s)
FAIL
FAIL gitea.dooplex.hu/admin/felhom-controller/cmd/controller 0.016s
FAIL
== RED-PROOF 4 (R-518): the v0.267.0 copy (12 apps / 8 minutes only)
=== RUN TestR518_BackupButtonStatesTheMeasuredDowntime
r518_backup_downtime_copy_test.go:33: hu: "kb. 6 perc" appears 0 times, want it on the page AND in the confirm
r518_backup_downtime_copy_test.go:33: hu: "nem másodperceket" appears 0 times, want it on the page AND in the confirm
r518_backup_downtime_copy_test.go:33: en: "about 6 minutes" appears 0 times, want it on the page AND in the confirm
r518_backup_downtime_copy_test.go:33: en: "not seconds" appears 0 times, want it on the page AND in the confirm
--- FAIL: TestR518_BackupButtonStatesTheMeasuredDowntime (0.07s)
FAIL
FAIL gitea.dooplex.hu/admin/felhom-controller/internal/web 0.080s
FAIL
== restored
ok gitea.dooplex.hu/admin/felhom-controller/internal/backup 0.013s
ok gitea.dooplex.hu/admin/felhom-controller/cmd/controller 0.014s
ok gitea.dooplex.hu/admin/felhom-controller/internal/web 0.080s
@@ -0,0 +1,7 @@
== 9202 teardown of the throwaway bookstack (installed 09:35:56Z for the R-519 reproduction), 2026-10-05T11:44:31Z
stop: HTTP 200
remove: HTTP 200
containers: 0
volumes: 0
stackdir: none
backups: none
@@ -0,0 +1,9 @@
Oct 05 12:54:08 demo-felhom felhom-os-apply[4115602]: os-apply: BUNDLE START agent=0.146.1-step1 sha=8482851ec27030a7 authority=signed files=22 write=1 same=20 kept=1 skipped=0
Oct 05 12:54:08 demo-felhom felhom-os-apply[4115603]: os-apply: BUNDLE WROTE /usr/local/sbin/felhom-os-apply (replaced)
Oct 05 12:54:09 demo-felhom felhom-os-apply[4115702]: os-apply: BUNDLE DONE agent=0.146.1-step1 written=1 same=20 self-check=ok signers-created=False
Oct 05 13:09:08 demo-felhom felhom-os-apply[4131155]: os-apply: BUNDLE START agent=0.146.1 sha=42333e969028867a authority=signed files=26 write=3 same=22 kept=1 skipped=0
Oct 05 13:09:08 demo-felhom felhom-os-apply[4131156]: os-apply: BUNDLE WROTE /usr/local/sbin/felhom-selfupdate-guarded (replaced)
Oct 05 13:09:08 demo-felhom felhom-os-apply[4131157]: os-apply: BUNDLE WROTE /usr/local/sbin/felhom-priv-apply (new)
Oct 05 13:09:08 demo-felhom felhom-os-apply[4131158]: os-apply: BUNDLE WROTE /etc/sudoers.d/felhom-agent (replaced)
Oct 05 13:09:09 demo-felhom felhom-os-apply[4131255]: os-apply: BUNDLE DONE agent=0.146.1 written=3 same=22 self-check=ok signers-created=False
Oct 05 13:09:09 demo-felhom felhom-agent[4101006]: time=2026-10-05T13:09:09.974+02:00 level=WARN msg="osupdate: capability probe after the config bundle" ok=67 total=67 degraded=""
@@ -0,0 +1,6 @@
Oct 05 12:42:18 demo-hp felhom-os-apply[500347]: os-apply: BUNDLE START agent=0.146.1-step1 sha=8482851ec27030a7 authority=signed files=22 write=1 same=20 kept=1 skipped=0
Oct 05 12:42:19 demo-hp felhom-os-apply[500348]: os-apply: BUNDLE WROTE /usr/local/sbin/felhom-os-apply (replaced)
Oct 05 12:42:19 demo-hp felhom-os-apply[500485]: os-apply: BUNDLE DONE agent=0.146.1-step1 written=1 same=20 self-check=ok signers-created=False
Oct 05 12:42:19 demo-hp felhom-agent[453394]: time=2026-10-05T12:42:19.875+02:00 level=INFO msg="osupdate: wrapper" line="os-apply: BUNDLE START agent=0.146.1-step1 sha=8482851ec27030a7 authority=signed files=22 write=1 same=20 kept=1 skipped=0"
Oct 05 12:42:19 demo-hp felhom-agent[453394]: time=2026-10-05T12:42:19.875+02:00 level=INFO msg="osupdate: wrapper" line="os-apply: BUNDLE WROTE /usr/local/sbin/felhom-os-apply (replaced)"
Oct 05 12:42:19 demo-hp felhom-agent[453394]: time=2026-10-05T12:42:19.876+02:00 level=INFO msg="osupdate: wrapper" line="os-apply: BUNDLE DONE agent=0.146.1-step1 written=1 same=20 self-check=ok signers-created=False"
@@ -0,0 +1,8 @@
== demo-felhom 2026-10-05T11:09:48Z: the checker as the agent user on the real staged files (SAME = nothing changes)
unit mnt-hdd_1.mount rc=3
wg rc=0
sshd-config rc=0
sshd-key rc=0
old route: sudo: a password is required
active active active active
5
@@ -0,0 +1,20 @@
== demo-hp 2026-10-05T10:58:16Z: the checker run AS the agent user through sudo, on the real staged files (each must be SAME — nothing changes)
unit mnt-hdd_1.mount rc=0
wg rc=0
sshd-config rc=0
sshd-key rc=0
dnsmasq rc=0
== an ATTACK, live: the agent stages a unit binding its own dir over /etc/sudoers.d (name and Where agree)
attack rc=3 (installed? no)
== the old route, live: sudo -n install of a staged file
sudo: a password is required
== journal
Oct 05 12:58:08 demo-hp felhom-priv-apply[550409]: felhom-priv-apply: SAME sshd-key /etc/felhom-sshd/authorized_keys/felhom-op
Oct 05 12:58:09 demo-hp felhom-priv-apply[550431]: felhom-priv-apply: SAME sshd-config /etc/felhom-sshd/sshd_config
Oct 05 12:58:16 demo-hp felhom-priv-apply[550931]: felhom-priv-apply: SAME unit /etc/systemd/system/mnt-hdd_1.mount
Oct 05 12:58:16 demo-hp felhom-priv-apply[550937]: felhom-priv-apply: SAME wg /etc/wireguard/wg-felhom.conf
Oct 05 12:58:16 demo-hp felhom-priv-apply[550962]: felhom-priv-apply: SAME sshd-config /etc/felhom-sshd/sshd_config
Oct 05 12:58:16 demo-hp felhom-priv-apply[550968]: felhom-priv-apply: SAME sshd-key /etc/felhom-sshd/authorized_keys/felhom-op
Oct 05 12:58:16 demo-hp felhom-priv-apply[550981]: felhom-priv-apply: SAME dnsmasq /etc/dnsmasq.d/felhom-resolver-base.conf
Oct 05 12:58:16 demo-hp felhom-priv-apply[550992]: felhom-priv-apply: REFUSED [U3] unit mnt-..-etc-sudoers.d.mount: mnt-..-etc-sudoers.d.mount: Where=/mnt/../etc/sudoers.d is not /mnt/<name> or /mnt/felhom-drives/<name>
@@ -0,0 +1,2 @@
== LIVE AFTER, demo-felhom, 2026-10-05T11:09:46Z: bundle 0.146.1; 'sudo -l -U felhom-agent <argv>' per case (lists only)
RESULT ok=93 fail=0
@@ -0,0 +1,2 @@
== LIVE AFTER, demo-hp, 2026-10-05T10:57:55Z: bundle 0.146.1, sudo Sudo version 1.9.16p2; 'sudo -l -U felhom-agent <argv>' per case (lists only)
RESULT ok=93 fail=0
@@ -0,0 +1,28 @@
== LIVE BEFORE, demo-felhom (felhom-pve), 2026-10-05T10:54:01Z: bundle 0.145.0 — the v0.145.0 sudoers; 'sudo -l -U felhom-agent <argv>' per case (lists only, runs nothing)
FAIL want=ALLOW got=DENY :: '/usr/local/sbin/felhom-priv-apply' 'unit' 'mnt-felhom\x2dx.mount'
FAIL want=ALLOW got=DENY :: '/usr/local/sbin/felhom-priv-apply' 'dnsmasq' '/tmp/felhom-resolver-123456789.conf' 'felhom-x.conf'
FAIL want=ALLOW got=DENY :: '/usr/local/sbin/felhom-priv-apply' 'wg'
FAIL want=DENY got=ALLOW :: /usr/sbin/pct set 9201 --dev0 /dev/sda -onboot 1
FAIL want=DENY got=ALLOW :: /usr/sbin/pct set 9201 --dev0 /dev/sda -mp8 /mnt/felhom-drives
FAIL want=DENY got=ALLOW :: /usr/sbin/pct set 9201 --delete mp0 --dev0 /dev/sda
FAIL want=DENY got=ALLOW :: /usr/sbin/pct set 9201 -mp0 /var/lib/felhom-agent/guests/9201/bootstrap,mp=/x --dev0 /dev/sda
FAIL want=DENY got=ALLOW :: /usr/bin/mount --bind /mnt/../var/lib/felhom-agent/x/felhom-data /mnt/felhom-drives/x
FAIL want=DENY got=ALLOW :: /usr/bin/mount --bind /mnt/a/felhom-data /mnt/felhom-drives/../../etc/sudoers.d
FAIL want=DENY got=ALLOW :: /usr/bin/umount /mnt/felhom-drives/x /
FAIL want=DENY got=ALLOW :: /usr/bin/mkdir -p /mnt/felhom-drives/x /etc/systemd/system/evil.mount
FAIL want=DENY got=ALLOW :: /usr/bin/install -o root -g root -m 0644 -- /var/lib/felhom-agent/units/x.mount /etc/systemd/system/etc-sudoers.d.mount
FAIL want=DENY got=ALLOW :: /usr/bin/install -m 0755 -- /tmp/felhom-guest-hook-1.sh /var/lib/vz/snippets/felhom-guest-hook.sh
FAIL want=DENY got=ALLOW :: /usr/bin/install -m 0755 -- /tmp/felhom-shared-parent-1.sh /usr/local/sbin/felhom-shared-parent.sh
FAIL want=DENY got=ALLOW :: /usr/bin/install -m 0644 /tmp/felhom-resolver-1.conf /etc/dnsmasq.d/felhom-x.conf
FAIL want=DENY got=ALLOW :: /usr/bin/install -o root -g root -m 0600 -- /var/lib/felhom-agent/wg/wg-felhom.conf /etc/wireguard/wg-felhom.conf
FAIL want=DENY got=ALLOW :: /usr/bin/install -o root -g root -m 0644 -- /var/lib/felhom-agent/felhom-sshd/sshd_config /etc/felhom-sshd/sshd_config
FAIL want=DENY got=ALLOW :: /usr/local/sbin/felhom-selfupdate-guarded apply /var/lib/felhom-agent/selfupdate/felhom-agent-9.9.9 0000000000000000000000000000000000000000000000000000000000000000
FAIL want=DENY got=ALLOW :: /usr/bin/systemctl enable --now -- etc-sudoers.d.mount
FAIL want=DENY got=ALLOW :: /usr/bin/rm -f /etc/systemd/system/mnt-felhomx /etc/passwd
FAIL want=DENY got=ALLOW :: /usr/bin/rmdir /mnt/felhom-drives/x /etc
FAIL want=DENY got=ALLOW :: /usr/sbin/nft add element inet felhom_oob operator_ips { 10.77.0.250 } ';' flush ruleset
FAIL want=DENY got=ALLOW :: /usr/sbin/smartctl -a -j /dev/sda -s off
FAIL want=DENY got=ALLOW :: /usr/sbin/lvs --reportformat json --units b -o lv_name,data_percent,metadata_percent -- pve/data --config x
FAIL want=DENY got=ALLOW :: /usr/sbin/pct exec 9201 --keep-env -- docker inspect -f x felhom-controller
FAIL want=DENY got=ALLOW :: /usr/sbin/pct unlock 9201 --whatever
RESULT ok=67 fail=26
@@ -0,0 +1,30 @@
== hp 2026-10-05T09:57:31Z — felhom-priv-apply --check against the box's LIVE files (read only; prints OK or the rule, never content)
unit mnt-hdd_1.mount: OK
dnsmasq felhom-demo-hp.conf: OK
dnsmasq felhom-guest-9201.conf: OK
dnsmasq felhom-resolver-base.conf: OK
wg: OK
sshd-config: OK
sshd-key: OK
== felhom-pve 2026-10-05T09:57:32Z — felhom-priv-apply --check against the box's LIVE files (read only; prints OK or the rule, never content)
unit mnt-hdd_1.mount: OK
dnsmasq felhom-guest-9201.conf: OK
dnsmasq felhom-resolver-base.conf: OK
wg: OK
sshd-config: OK
sshd-key: OK
== hp 2026-10-05T10:13:26Z — v0.146.1 checker, --check against LIVE files (read only)
unit mnt-hdd_1.mount: OK
dnsmasq felhom-demo-hp.conf: OK
dnsmasq felhom-guest-9201.conf: OK
dnsmasq felhom-resolver-base.conf: OK
wg: OK
sshd-config: OK
sshd-key: OK
== felhom-pve 2026-10-05T10:13:27Z — v0.146.1 checker, --check against LIVE files (read only)
unit mnt-hdd_1.mount: OK
dnsmasq felhom-guest-9201.conf: OK
dnsmasq felhom-resolver-base.conf: OK
wg: OK
sshd-config: OK
sshd-key: OK
@@ -0,0 +1,85 @@
== RED-PROOF F1 (mount units): felhom-priv-apply stops checking Where
test_U3_bind_over_sudoers_dir (__main__.Refuses.test_U3_bind_over_sudoers_dir) ... ok
test_U3_name_must_match_where (__main__.Refuses.test_U3_name_must_match_where) ... ok
test_U3_network_outside_drives (__main__.Refuses.test_U3_network_outside_drives) ... ok
test_U3_traversal_in_where (__main__.Refuses.test_U3_traversal_in_where) ... ok
OK
== RED-PROOF F2 (WireGuard): felhom-priv-apply allows any key
FAIL: test_W1_postup (__main__.Refuses.test_W1_postup)
FAILED (failures=1)
== RED-PROOF F3 (self-update, root side): felhom-os-apply agent_update skips the signature
FAILED (errors=1)
== RED-PROOF F4 (self-update, agent side): the agent calls felhom-selfupdate-guarded apply itself again
--- FAIL: TestExecutor_HappyPath (0.00s)
executor_test.go:111: execute: agent_update: the root wrapper did not apply it: <nil> (report: ; stderr: )
FAIL
== RED-PROOF F5 (guest hook): SnippetReady accepts any content
--- FAIL: TestSnippetReady (0.00s)
install_test.go:43: a hook with other content read as ready
FAIL
== RED-PROOF F6 (shared parent): the agent installs the boot script from /tmp again when it differs
--- FAIL: TestSharedParentBoot_NeverInstalls (0.00s)
intermediary_install_test.go:54: both missing: the agent ran [[install -m 0755 -- /tmp/felhom-shared-parent-x.sh /tmp/TestSharedParentBoot_NeverInstalls2355530825/001/felhom-shared-parent.sh]] — it must install nothing (R-861)
intermediary_install_test.go:54: script differs: the agent ran [[install -m 0755 -- /tmp/felhom-shared-parent-x.sh /tmp/TestSharedParentBoot_NeverInstalls2355530825/002/felhom-shared-parent.sh]] — it must install nothing (R-861)
intermediary_install_test.go:54: unit missing: the agent ran [[install -m 0755 -- /tmp/felhom-shared-parent-x.sh /tmp/TestSharedParentBoot_NeverInstalls2355530825/003/felhom-shared-parent.sh]] — it must install nothing (R-861)
== RED-PROOF F7 (escrow, root read): a staged file is read with os.ReadFile (follows a symlink)
--- FAIL: TestAttach_RefusesASymlink (0.00s)
r861_staged_read_test.go:24: a symlinked staged file was read: ok=true err=<nil> value-set=true
FAIL
== RED-PROOF F8 (network shares): nosuid,nodev dropped from the NFS options
--- FAIL: TestPrivApply_AcceptsTheRenderedUnits (0.35s)
r861_privapply_contract_test.go:31: media .mount: REFUSED [U5] mnt-felhom\x2ddrives-media.mount: a network share must carry nosuid,nodev
FAIL
== RED-PROOF F9 (the exact patterns): the v0.145.0 sudoers under the injection test
injections the old file allows (Go matcher): 23
== restored — the same tests green
ok gitea.dooplex.hu/admin/felhom-agent/internal/selfupdate (cached)
ok gitea.dooplex.hu/admin/felhom-agent/internal/guesthook (cached)
ok gitea.dooplex.hu/admin/felhom-agent/internal/localapi (cached)
ok gitea.dooplex.hu/admin/felhom-agent/internal/escrow (cached)
ok gitea.dooplex.hu/admin/felhom-agent/internal/storage 0.474s
ok gitea.dooplex.hu/admin/felhom-agent/internal/capability 0.177s
OK
OK
== RED-PROOF F1 (re-run): the first run did NOT convict — the name check (escape(Where)==name) masked it. The test now uses the
pair that only the Where rule stops: name mnt-..-etc.mount + Where=/mnt/../etc (= /etc). Mutation: stop checking Where
FAIL: test_U3_traversal_in_where (__main__.Refuses.test_U3_traversal_in_where)
FAILED (failures=1)
== RED-PROOF F3 (re-run, clean assertion): felhom-os-apply skips the signature
FAIL: test_a_bad_signature_never_reaches_the_wrapper (__main__.AgentUpdate.test_a_bad_signature_never_reaches_the_wrapper)
AssertionError: None is not true : a job whose signature does not verify was NOT refused: {'agent_update': {'sha256': 'd76b02acf626ce399da7e0a9e17b35563227a4831e14f5edca4ab7cf89eb2c79', 'version': '0.146.0', 'wrapper': '', 'wrapper_rc': 0}, 'layer': 'host', 'mode': 'agent_update', 'pass_seconds': 0.0, 'refused': None, 'release_id': 'agent-0.146.0', 'vmid': 0}
FAILED (failures=1)
== restored
OK
OK
=== Review findings 2026-10-05 (background security review of commit 6ab1e7c) — fixed in v0.146.1, each red-proved
== RED-PROOF S1 (TOCTOU): the wrapper gets the agent's path again (hash, then copy by path)
FAIL: test_signed_update_flips_and_burns_the_nonce (__main__.AgentUpdate.test_signed_update_flips_and_burns_the_nonce)
FAILED (failures=1)
== RED-PROOF S1b: the A/B wrapper accepts the agent's staging dir again
FAIL: test_the_agents_staging_dir_is_refused (__main__.SelfupdateWrapperConfinement.test_the_agents_staging_dir_is_refused)
FAILED (failures=1)
== RED-PROOF S2 (allowlist escape): [Unit] accepts Wants=/Requires=/Before= again
FAIL: test_U2_wants_starts_another_unit (__main__.Refuses.test_U2_wants_starts_another_unit)
FAILED (failures=1)
== RED-PROOF S3 (path traversal): open the whole path with O_NOFOLLOW only
--- FAIL: TestAttach_RefusesASymlinkedDirectory (0.00s)
r861_staged_read_test.go:54: a key behind a symlinked directory was read: ok=true err=<nil>
FAIL
== restored
OK
OK
ok gitea.dooplex.hu/admin/felhom-agent/internal/escrow 0.008s
@@ -0,0 +1,4 @@
== the R-880 step bundle, 2026-10-05T10:24:08Z: base = felhom-agent/0.145.0/felhom-config-bundle.json (sha 78c00adc…, what demo-hp, demo-felhom, tester-1 run); built by scripts/build-step-bundle.py at agent e4b5cf9; published as felhom-agent/0.146.1-step1/felhom-config-bundle.json
step sha 8482851ec27030a7615216048338b1c8939d4e353023ad11c66e6f8930af8613
round trip sha 8482851ec27030a7615216048338b1c8939d4e353023ad11c66e6f8930af8613
same paths: True changed: ['/usr/local/sbin/felhom-os-apply'] version: 0.146.1-step1
@@ -0,0 +1,129 @@
== R-861 real-sudo proof, sudo 1.9.16p2 (debian:trixie throwaway container on DooPlex, 2026-10-05T09:58:38Z); 'sudo -l -U felhom-agent <argv>' per case
-- NEW sudoers (agent v0.146.0): every capability must be ALLOW, every attack DENY
ok ALLOW '/usr/bin/lxc-info' '-n' '9201' '-p' '-H'
ok ALLOW '/usr/bin/mount' '--bind' '/mnt/felhom-drives' '/mnt/felhom-drives'
ok ALLOW '/usr/bin/mount' '--make-shared' '/mnt/felhom-drives'
ok ALLOW '/usr/bin/mount' '--make-private' '/mnt/felhom-drives'
ok ALLOW '/usr/bin/mount' '--bind' '/mnt/felhom-usb/felhom-data' '/mnt/felhom-drives/felhom-usb'
ok ALLOW '/usr/bin/umount' '/mnt/felhom-drives/felhom-usb'
ok ALLOW '/usr/bin/mkdir' '-p' '/mnt/felhom-drives'
ok ALLOW '/usr/bin/mkdir' '-p' '/mnt/felhom-drives/felhom-usb'
ok ALLOW '/usr/bin/mkdir' '-p' '/mnt/felhom-usb/felhom-data'
ok ALLOW '/usr/bin/chown' '100000:100000' '/mnt/felhom-usb/felhom-data'
ok ALLOW '/usr/bin/systemctl' 'enable' 'felhom-shared-parent.service'
ok ALLOW '/usr/sbin/pct' 'set' '9201' '-mp8' '/mnt/felhom-drives,mp=/mnt/felhom-drives'
ok ALLOW '/usr/sbin/blkid' '-p' '-o' 'export' '/dev/sda'
ok ALLOW '/usr/bin/lsblk' '-J' '-o' 'NAME,FSTYPE,PTTYPE,MOUNTPOINT' '/dev/sda'
ok ALLOW '/usr/local/sbin/felhom-mkfs-guarded' '/dev/sda' 'ext4'
ok ALLOW '/usr/local/sbin/felhom-mkfs-guarded' '/dev/sda' 'xfs'
ok ALLOW '/usr/sbin/smartctl' '-a' '-j' '/dev/sda'
ok ALLOW '/usr/sbin/lvs' '--reportformat' 'json' '--units' 'b' '-o' 'lv_name,data_percent,metadata_percent' '--' 'pve/data'
ok ALLOW '/usr/local/sbin/felhom-priv-apply' 'unit' 'mnt-felhom\x2dx.mount'
ok ALLOW '/usr/bin/systemctl' 'daemon-reload'
ok ALLOW '/usr/bin/systemctl' 'enable' '--now' '--' 'mnt-felhom\x2dx.mount'
ok ALLOW '/usr/bin/systemctl' 'disable' '--' 'mnt-felhom\x2dx.mount'
ok ALLOW '/usr/bin/systemctl' 'stop' '--' 'mnt-felhom\x2dx.mount'
ok ALLOW '/usr/bin/systemctl' 'reset-failed' '--' 'mnt-felhom\x2ddrives-media.automount'
ok ALLOW '/usr/bin/rmdir' '/mnt/felhom-drives/media'
ok ALLOW '/usr/bin/systemctl' 'start' 'networking.service'
ok ALLOW '/usr/bin/chown' '-R' '100000:100000' '/var/lib/felhom-agent/guests/9201'
ok ALLOW '/usr/sbin/pct' 'set' '9201' '-mp0' '/var/lib/felhom-agent/guests/9201/bootstrap,mp=/etc/felhom-bootstrap,ro=1'
ok ALLOW '/usr/sbin/pct' 'set' '9201' '-onboot' '1'
ok ALLOW '/usr/sbin/pct' 'set' '9201' '--hookscript' 'local:snippets/felhom-guest-hook.sh'
ok ALLOW '/usr/sbin/pct' 'set' '9201' '--delete' 'mp0'
ok ALLOW '/usr/sbin/pct' 'reboot' '9201'
ok ALLOW '/usr/bin/apt-get' 'install' '-y' '-q' 'dnsmasq'
ok ALLOW '/usr/local/sbin/felhom-priv-apply' 'dnsmasq' '/tmp/felhom-resolver-123456789.conf' 'felhom-x.conf'
ok ALLOW '/usr/bin/systemctl' 'enable' '--now' 'dnsmasq'
ok ALLOW '/usr/local/sbin/felhom-os-apply' '--plan' '/var/lib/felhom-agent/os/plan-x.json'
ok ALLOW '/usr/bin/systemctl' 'reload' 'dnsmasq'
ok ALLOW '/usr/bin/systemctl' 'restart' 'dnsmasq'
ok ALLOW '/usr/bin/rm' '-f' '/etc/dnsmasq.d/felhom-x.conf'
ok ALLOW '/usr/sbin/pct' 'exec' '9201' '--' 'ip' '-4' '-o' 'addr' 'show' 'dev' 'eth0'
ok ALLOW '/usr/sbin/pct' 'exec' '9201' '--' 'docker' 'exec' 'felhom-controller' 'cat' '/opt/docker/felhom-controller/controller.yaml'
ok ALLOW '/usr/sbin/pct' 'exec' '9201' '--' 'ip' 'route' 'show' 'default'
ok ALLOW '/usr/sbin/pct' 'exec' '9201' '--' 'cat' '/etc/network/interfaces'
ok ALLOW '/usr/sbin/pct' 'exec' '9201' '--' 'pgrep' '-x' 'dhclient'
ok ALLOW '/usr/sbin/pct' 'exec' '9201' '--' 'dhclient' '-pf' '/run/dhclient.eth0.pid' '-lf' '/var/lib/dhcp/dhclient.eth0.leases' 'eth0'
ok ALLOW '/usr/sbin/pct' 'exec' '9201' '--' 'cat' '/etc/felhom-controller-image'
ok ALLOW '/usr/sbin/pct' 'exec' '9201' '--' 'docker' 'image' 'inspect' 'gitea.dooplex.hu/admin/felhom-controller:0.0.0'
ok ALLOW '/usr/sbin/pct' 'exec' '9201' '--' 'docker' 'inspect' '-f' '{{.State.Running}}' 'felhom-controller'
ok ALLOW '/usr/sbin/pct' 'exec' '9201' '--' 'systemctl' 'restart' 'felhom-controller-bootstrap.service'
ok ALLOW '/usr/sbin/pct' 'exec' '9201' '--' 'tee' '/etc/felhom-controller-image'
ok ALLOW '/usr/sbin/pct' 'unlock' '9201'
ok ALLOW '/usr/bin/apt-get' 'install' '-y' '-q' 'wireguard-tools'
ok ALLOW '/usr/local/sbin/felhom-priv-apply' 'wg'
ok ALLOW '/usr/bin/systemctl' 'enable' '--now' 'wg-quick@wg-felhom'
ok ALLOW '/usr/bin/systemctl' 'restart' 'wg-quick@wg-felhom'
ok ALLOW '/usr/bin/systemctl' 'disable' '--now' 'wg-quick@wg-felhom'
ok ALLOW '/usr/bin/wg' 'show' 'wg-felhom' 'latest-handshakes'
ok ALLOW '/usr/local/sbin/felhom-pbs-apply' 'create' 'felhom-pbs' '10.77.0.1' 'felhom-offsite' 'ns0' 'felhom@pbs!ns0' '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00' '/etc/pve/priv/storage'
ok ALLOW '/usr/local/sbin/felhom-pbs-apply' 'reconcile' 'felhom-pbs' '10.77.0.1' 'ns0' 'felhom@pbs!ns0' '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00' '/etc/pve/priv/storage'
ok ALLOW '/usr/local/sbin/felhom-pbs-apply' 'grant' 'felhom-pbs'
ok ALLOW '/usr/local/sbin/felhom-pbs-apply' 'read' 'felhom-pbs' '/etc/pve/priv/storage'
ok ALLOW '/usr/local/bin/felhom-agent' '--config' '/etc/felhom-agent/agent.json' '--selftest=escrow-create' '--upload' '--output=json'
ok ALLOW '/usr/local/sbin/felhom-selfupdate-guarded' 'commit'
ok ALLOW '/usr/local/sbin/felhom-selfupdate-guarded' 'rollback'
ok DENY /usr/sbin/pct set 9201 --dev0 /dev/sda -onboot 1
ok DENY /usr/sbin/pct set 9201 --dev0 /dev/sda -mp8 /mnt/felhom-drives
ok DENY /usr/sbin/pct set 9201 --delete mp0 --dev0 /dev/sda
ok DENY /usr/sbin/pct set 9201 -mp0 /var/lib/felhom-agent/guests/9201/bootstrap,mp=/x --dev0 /dev/sda
ok DENY /usr/bin/mount --bind /mnt/../var/lib/felhom-agent/x/felhom-data /mnt/felhom-drives/x
ok DENY /usr/bin/mount --bind /mnt/a/felhom-data /mnt/felhom-drives/../../etc/sudoers.d
ok DENY /usr/bin/umount /mnt/felhom-drives/x /
ok DENY /usr/bin/chown 100000:100000 /mnt/a/felhom-data /etc/shadow
ok DENY /usr/bin/mkdir -p /mnt/felhom-drives/x /etc/systemd/system/evil.mount
ok DENY /usr/bin/install -o root -g root -m 0644 -- /var/lib/felhom-agent/units/x.mount /etc/systemd/system/etc-sudoers.d.mount
ok DENY /usr/bin/install -m 0755 -- /tmp/felhom-guest-hook-1.sh /var/lib/vz/snippets/felhom-guest-hook.sh
ok DENY /usr/bin/install -m 0755 -- /tmp/felhom-shared-parent-1.sh /usr/local/sbin/felhom-shared-parent.sh
ok DENY /usr/bin/install -m 0644 /tmp/felhom-resolver-1.conf /etc/dnsmasq.d/felhom-x.conf
ok DENY /usr/bin/install -o root -g root -m 0600 -- /var/lib/felhom-agent/wg/wg-felhom.conf /etc/wireguard/wg-felhom.conf
ok DENY /usr/bin/install -o root -g root -m 0644 -- /var/lib/felhom-agent/felhom-sshd/sshd_config /etc/felhom-sshd/sshd_config
ok DENY /usr/local/sbin/felhom-selfupdate-guarded apply /var/lib/felhom-agent/selfupdate/felhom-agent-9.9.9 0000000000000000000000000000000000000000000000000000000000000000
ok DENY /usr/bin/systemctl enable --now -- mnt-hdd_1.mount evil.service
ok DENY /usr/bin/systemctl enable --now -- etc-sudoers.d.mount
ok DENY /usr/bin/rm -f /etc/systemd/system/mnt-felhomx /etc/passwd
ok DENY /usr/bin/rm -f /etc/dnsmasq.d/felhom-x.conf /etc/shadow
ok DENY /usr/bin/rmdir /mnt/felhom-drives/x /etc
ok DENY /usr/sbin/nft add element inet felhom_oob operator_ips { 10.77.0.250 } ';' flush ruleset
ok DENY /usr/sbin/smartctl -a -j /dev/sda -s off
ok DENY /usr/sbin/lvs --reportformat json --units b -o lv_name,data_percent,metadata_percent -- pve/data --config x
ok DENY /usr/sbin/pct exec 9201 --keep-env -- docker inspect -f x felhom-controller
ok DENY /usr/sbin/pct unlock 9201 --whatever
ok DENY /usr/local/sbin/felhom-priv-apply unit ../../etc/x.mount
ok DENY /usr/local/sbin/felhom-priv-apply dnsmasq /etc/shadow felhom-x.conf
ok DENY /usr/local/sbin/felhom-priv-apply wg /etc/shadow
rc=0
-- OLD sudoers (agent v0.145.0), the same attacks (this side is the red-proof: 'FAIL want=ALLOW got=DENY' means the OLD file already refused that one; 'ok ALLOW' means the old file let it through)
ok ALLOW /usr/sbin/pct set 9201 --dev0 /dev/sda -onboot 1
ok ALLOW /usr/sbin/pct set 9201 --dev0 /dev/sda -mp8 /mnt/felhom-drives
ok ALLOW /usr/sbin/pct set 9201 --delete mp0 --dev0 /dev/sda
ok ALLOW /usr/sbin/pct set 9201 -mp0 /var/lib/felhom-agent/guests/9201/bootstrap,mp=/x --dev0 /dev/sda
ok ALLOW /usr/bin/mount --bind /mnt/../var/lib/felhom-agent/x/felhom-data /mnt/felhom-drives/x
ok ALLOW /usr/bin/mount --bind /mnt/a/felhom-data /mnt/felhom-drives/../../etc/sudoers.d
ok ALLOW /usr/bin/umount /mnt/felhom-drives/x /
FAIL want=ALLOW got=DENY :: /usr/bin/chown 100000:100000 /mnt/a/felhom-data /etc/shadow
ok ALLOW /usr/bin/mkdir -p /mnt/felhom-drives/x /etc/systemd/system/evil.mount
ok ALLOW /usr/bin/install -o root -g root -m 0644 -- /var/lib/felhom-agent/units/x.mount /etc/systemd/system/etc-sudoers.d.mount
ok ALLOW /usr/bin/install -m 0755 -- /tmp/felhom-guest-hook-1.sh /var/lib/vz/snippets/felhom-guest-hook.sh
ok ALLOW /usr/bin/install -m 0755 -- /tmp/felhom-shared-parent-1.sh /usr/local/sbin/felhom-shared-parent.sh
ok ALLOW /usr/bin/install -m 0644 /tmp/felhom-resolver-1.conf /etc/dnsmasq.d/felhom-x.conf
ok ALLOW /usr/bin/install -o root -g root -m 0600 -- /var/lib/felhom-agent/wg/wg-felhom.conf /etc/wireguard/wg-felhom.conf
ok ALLOW /usr/bin/install -o root -g root -m 0644 -- /var/lib/felhom-agent/felhom-sshd/sshd_config /etc/felhom-sshd/sshd_config
ok ALLOW /usr/local/sbin/felhom-selfupdate-guarded apply /var/lib/felhom-agent/selfupdate/felhom-agent-9.9.9 0000000000000000000000000000000000000000000000000000000000000000
FAIL want=ALLOW got=DENY :: /usr/bin/systemctl enable --now -- mnt-hdd_1.mount evil.service
ok ALLOW /usr/bin/systemctl enable --now -- etc-sudoers.d.mount
ok ALLOW /usr/bin/rm -f /etc/systemd/system/mnt-felhomx /etc/passwd
FAIL want=ALLOW got=DENY :: /usr/bin/rm -f /etc/dnsmasq.d/felhom-x.conf /etc/shadow
ok ALLOW /usr/bin/rmdir /mnt/felhom-drives/x /etc
ok ALLOW /usr/sbin/nft add element inet felhom_oob operator_ips { 10.77.0.250 } ';' flush ruleset
ok ALLOW /usr/sbin/smartctl -a -j /dev/sda -s off
ok ALLOW /usr/sbin/lvs --reportformat json --units b -o lv_name,data_percent,metadata_percent -- pve/data --config x
ok ALLOW /usr/sbin/pct exec 9201 --keep-env -- docker inspect -f x felhom-controller
ok ALLOW /usr/sbin/pct unlock 9201 --whatever
FAIL want=ALLOW got=DENY :: /usr/local/sbin/felhom-priv-apply unit ../../etc/x.mount
FAIL want=ALLOW got=DENY :: /usr/local/sbin/felhom-priv-apply dnsmasq /etc/shadow felhom-x.conf
FAIL want=ALLOW got=DENY :: /usr/local/sbin/felhom-priv-apply wg /etc/shadow
rc=1
@@ -0,0 +1,11 @@
== hub System page after delivery, 2026-10-05T11:43:34Z: per box — agent cell, root-files cell (raw)
Tester-2-be8404 | agent: 0.142.0 → 0.146.1 (since 2026-10-05) | root files: []
demo-felhom-8363b5 | agent: 0.146.1 | root files: ['0.146.1']
demo-hp-bb76ea | agent: 0.146.1 | root files: ['0.146.1']
tester-1-d70be4 | agent: 0.146.1 | root files: ['0.146.1']
tester-1-d70be4: Capabilities Capability Status Feature / reason controllerswap-image-inspect critical ok controller-swap / managed auto-update controllerswap-inspect critical ok controller-swap / managed auto-update controllerswap-read
demo-hp-bb76ea: Capabilities Capability Status Feature / reason controllerswap-image-inspect critical ok controller-swap / managed auto-update controllerswap-inspect critical ok controller-swap / managed auto-update controllerswap-read
demo-felhom-8363b5: Capabilities Capability Status Feature / reason controllerswap-image-inspect critical ok controller-swap / managed auto-update controllerswap-inspect critical ok controller-swap / managed auto-update controllerswap-read
tester-1-d70be4: capability rows 66 {'ok': 66} degraded: []
demo-hp-bb76ea: capability rows 66 {'ok': 66} degraded: []
demo-felhom-8363b5: capability rows 67 {'ok': 67} degraded: []
@@ -0,0 +1,7 @@
== floors 2026-10-05T10:24:56Z: POST /customers/<id>/floor min_controller_version=0.296.0 min_agent=0.131.0
demo-hp: Location: /customers/demo-hp?flash=floor_set
demo-felhom: Location: /customers/demo-felhom?flash=floor_set
tester-1: Location: /customers/tester-1?flash=floor_set
2026/10/05 12:24:56 [INFO] Customer demo-hp controller-version floor override set to "0.296.0" (declared MinAgent "0.131.0")
2026/10/05 12:24:57 [INFO] Customer demo-felhom controller-version floor override set to "0.296.0" (declared MinAgent "0.131.0")
2026/10/05 12:24:57 [INFO] Customer tester-1 controller-version floor override set to "0.296.0" (declared MinAgent "0.131.0")
@@ -0,0 +1,10 @@
== agent_update 0.146.1 (sha badd6c9a…) signed with felhom-op-1, ttl 45m, 2026-10-05T10:25:10Z
-- demo-hp-bb76ea
wrote envelope to /tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/ea20e5ca-a93a-4939-bd73-dd472bcf0590/scratchpad/env-demo-hp-bb76ea-agent_update.json
uploaded signed op to the hub jobs queue
-- demo-felhom-8363b5
wrote envelope to /tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/ea20e5ca-a93a-4939-bd73-dd472bcf0590/scratchpad/env-demo-felhom-8363b5-agent_update.json
uploaded signed op to the hub jobs queue
-- tester-1-d70be4
wrote envelope to /tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/ea20e5ca-a93a-4939-bd73-dd472bcf0590/scratchpad/env-tester-1-d70be4-agent_update.json
uploaded signed op to the hub jobs queue
@@ -0,0 +1,13 @@
== agent_config_update 0.146.1-step1 (bundle sha 8482851e…), demo-hp-bb76ea, 2026-10-05T10:35:43Z
wrote envelope to /tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/ea20e5ca-a93a-4939-bd73-dd472bcf0590/scratchpad/env-demo-hp-bb76ea-agent_config_update.json
uploaded signed op to the hub jobs queue
== agent_config_update 0.146.1 (bundle sha 42333e96…), demo-hp-bb76ea, 2026-10-05T10:42:50Z
uploaded signed op to the hub jobs queue
== agent_config_update 0.146.1-step1 (bundle sha 8482851e…), demo-felhom-8363b5, 2026-10-05T10:53:36Z
uploaded signed op to the hub jobs queue
== agent_config_update 0.146.1-step1 (bundle sha 8482851e…), tester-1-d70be4, 2026-10-05T10:53:36Z
uploaded signed op to the hub jobs queue
== agent_config_update 0.146.1 (bundle sha 42333e96…), demo-felhom-8363b5, 2026-10-05T10:58:57Z
uploaded signed op to the hub jobs queue
== agent_config_update 0.146.1 (bundle sha 42333e96…), tester-1-d70be4, 2026-10-05T11:13:04Z
uploaded signed op to the hub jobs queue
@@ -0,0 +1,4 @@
== vouch 2026-10-05T10:24:18Z: POST /configuration/artifacts (Basic + X-Felhom-Operator), agent 0.146.1, golden 0.296.0, min_agent 0.131.0
HTTP/1.1 303 See Other
Location: /configuration?flash=artifacts_set
2026/10/05 12:24:45 [INFO] Artifact manifest set: agent=0.146.1 golden=0.296.0 min_agent="0.131.0" wrapper_sha=false bundle_sha="42333e969028867ad8142335e6c1bc4040eec231de0d8d330c2d4b2cf7bc3442"