84f3f7ddb1
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
6.3 KiB
6.3 KiB
REPORT — v0.62.0: A1 pool-membership ownership check for the stale-lock reaper
Date: 2026-07-03 · Class: implementation (agent v0.61.0 → v0.62.0, live on felhom-pve)
Spec basis: audit A1 (AUDIT-blast-radius-hostroot-localapi-2026-07-02 §A) via the spike verdict
(felhom.eu/documentation/audits/SPIKE-a1-pool-membership-read-2026-07-03.md Q3/Q4/Q5).
Companion: felhom.eu felhom-host-install.sh v1.8.0 → v1.9.0 (Pool.Audit in FelhomAgentGuest).
1. Baselines & commits
| Repo | Baseline | Shipped commit |
|---|---|---|
| felhom-agent | 3e760a559a (v0.61.0) |
3f37c5f — v0.62.0 code + tests + REUSE/CHANGELOG/CONTEXT |
| felhom.eu | 5e3dfcb714 (script v1.8.0) |
639a57e — script v1.9.0 + scripts/CHANGELOG + REUSE §2 errata + CONTEXT |
2. What changed (agent)
Client.Pool(ctx, name)(proxmox/query.go):GET /pools/{name}→PoolInfo{PoolID, Members[]{VMID,Type}}. API-side read — theClient-never-shells /Privileged-never-HTTPs fence is untouched.staleLockController.Guests()(localapi/stalelock.go):ListLXC ∩ pool members(nonzero-vmid, non-storageentries). Pool-read failure → wrapped errorpool membership read (pool=felhom): …→ rides the pre-existing "guest list unavailable — skipping recovery" guard (stalelock.goServer path). NEVER falls back to the unfiltered list. One INFO line per scan:stale-lock: scanning pool guests(pool, listed, scanned). Note (spec deviation, by necessity): the spec located this line inRecoverStaleLockedGuests; it is emitted by the production controller instead, because the unchangedStaleLockControllerseam cannot carry the pool name + pre-intersect count to the Server without breaking the 9 existing fake-based tests. Net startup-log effect is identical (Scenario D line observed live, §5).NewStaleLockController(px, runner, pool, logger); main.go threadsreconcile.DefaultPool.- Capability: composed closure in main.go appends
pve:pool-read(non-critical) to the sudo prober's snapshot;internal/capability/untouched.--selftestgained a "pool read" line.
3. Test results (all green: go build ./... && go vet ./... && go test ./..., 17 pkgs ok)
| Test | Result |
|---|---|
TestStaleLock_ForeignGuestNotReaped (Scenario A) |
PASS — foreign 5000 never unlocked/snap-deleted/started |
TestStaleLock_PoolGuestStillReaped (Scenario B) |
PASS — owned 9201 fully recovered (unlock+delsnap+start) |
TestStaleLock_PoolReadFails_SkipsAll (Scenario C) |
PASS — zero mutations on pool-read error; error text names the pool read |
TestStaleLockController_GuestsIntersect (§8 edges) |
PASS — storage-type/vmid-0 members excluded; empty pool = empty scan, no error |
| 9 pre-existing stalelock tests | PASS unmodified (Server-level fakeStaleLock seam untouched by design) |
Red-proofs (both run and reverted):
- A: intersect replaced with raw-ListLXC passthrough →
TestStaleLock_ForeignGuestNotReapedFAILED withrunner calls=[[pct unlock 5000]](the exact A1 exploit) andTestStaleLockController_GuestsIntersectfailed with both vmids returned. Restored. - C: pool-read error changed to fall back to the unfiltered list →
TestStaleLock_PoolReadFails_SkipsAllFAILED withrunner=[[pct unlock 9201]] delsnap=[9201] started=[9201]. Restored.
4. Live rescope (felhom-pve, PUSHED v1.9.0 script fetched from Gitea raw, --rescope-acl --vmid 9201)
Roles after (only Guest changed; Base/Store byte-identical):
FelhomAgentStore → Datastore.Allocate,Datastore.AllocateSpace
FelhomAgentGuest → Pool.Allocate,Pool.Audit,VM.Allocate,VM.Audit,VM.Backup,VM.Config.CPU,
VM.Config.Disk,VM.Config.Memory,VM.Config.Network,VM.Config.Options,
VM.PowerMgmt,VM.Snapshot,VM.Snapshot.Rollback
FelhomAgentBase → Datastore.Audit,SDN.Use,Sys.Audit
Production-token GET /pools/felhom (403 in the spike) → HTTP 200, members [9201]. The running
v0.61.0 agent stayed active through the rescope (order: rescope BEFORE agent deploy, per spec).
5. Agent deploy verification (felhom-pve)
- Binary built on 180 from
3f37c5f, deployed with backupfelhom-agent.bak-0.61.0;felhom-agent --version→felhom-agent 0.62.0; serviceactive. - Startup log (live excerpt):
msg="capabilities self-check" ok=47 total=47 degraded=0(46 → 47: the newpve:pool-read, OK) andmsg="stale-lock: scanning pool guests" pool=felhom listed=1 scanned=1(listed=1 because the scoped token's ListLXC is already pool-filtered — spike T1, expected). ReassertGuestBinds clean (logs only on action/failure; none emitted). No new warnings — the pre-existing PBS token-secret WARN (BUNDLE leftover, CONTEXT open thread) is unchanged. --selftest:[ ok ] pool read pool "felhom", 1 member(s)/- 9201 type=lxc; suiteOK.- Hub-side: latest stored host report (agent_version 0.62.0) carries
{"name":"pve:pool-read","feature":"stale-lock recovery scoping (pool ownership check)", "critical":false,"status":"ok"}— 47 capabilities, 0 degraded. Verified against a snapshot of the hub DB (host_reports.report_json), since the operator UI is password-gated. - Published: Gitea generic package
felhom-agent/0.62.0, round-trip sha256 verified:ed1192c9ae8f355dee19128cf4b01984ba7398e860b4a16a8009655b84b32d81.
6. Operator follow-ups
- Hub Day-0 artifact manifest still vouches 0.60.0 → bump to 0.62.0 (sha256 above; password-gated UI — CC cannot).
- Rotation + de-git of the 3 committed secrets in
felhom.eu/manifests/felhom.secret.yaml(spike appendix; REUSE §2 errata now records thatgitea-credsis NOT out-of-band).
7. Observations (not acted on)
- Hub capability-name handling confirmed generic: the checker keys on
Critical && status=="degraded"only (hub/internal/monitor/host_capability.go), names are data — the new non-critical entry rides the report/UI without paging and needed no hub change. - The spike Q6 consumer observations (hub collect / disks / reconcile
ListLXCusers) remain observations; no other consumer touched, per spec. _state_get provisioned_vmidwas empty on felhom-pve (state.json holds onlycompleted), so--rescope-aclneeded the explicit--vmid 9201— worth knowing for other boxes.