Files
felhom-agent/REPORT.md
T

7.8 KiB
Raw Blame History

REPORT — blast-radius audit fixes B1 + D1 + D2 + D3 (v0.61.0)

Date: 2026-07-03 · Repo: felhom-agent · Class: Implementation (destructive-op guard surfaces D1/D3 proven in unit-land + a loop-device recorder harness — NO real mkfs, no live /dev race, per the §13 STOP). Source of truth: felhom.eu/documentation/audits/AUDIT-blast-radius-hostroot-localapi-2026-07-02.md.

1. Baseline → target

agent v0.60.0 (cc93dae, confirmed before starting) → v0.61.0. Commits on main:

  • 3f382bf — all four fixes + tests + harness + version bump + CHANGELOG/CONTEXT/README.
  • (this REPORT is the follow-up docs commit)

2. What shipped

Fix Files What changed
B1 random temp staging internal/guesthook/install.go, internal/localapi/intermediary.go (new stageTemp), configs/felhom-agent.sudoers, internal/capability/manifest.go Fixed, predictable /tmp names (local TOCTOU into root-executed hookscript/boot script) → os.CreateTemp random names (the lanresolver pattern). Sudoers install-SOURCE grants became globs (/tmp/felhom-guest-hook-*.sh, /tmp/felhom-shared-parent-*.{sh,service}); destinations + modes unchanged. Manifest repr vectors updated to match.
D1 wrapper asymmetry configs/felhom-mkfs-guarded.sh, new scripts/mkfs-guarded-harness.sh Additive re-checks: /sys/block/<disk>/ro==1 → die; lsblk-FSTYPE loop refusing exactly claim.go's memberFSTypes (LVM2_member/zfs_member/linux_raid_member/crypto_LUKS/swap); pvs via absolute candidates (/usr/sbin/pvs, /sbin/pvs) with the FSTYPE loop as the independent LVM catch when pvs is absent. Nothing removed/reordered; a blank partitioned disk still formats.
D2 empty-lsblk fail-safe internal/storage/claim.go classifyClaim refuses when the node tree is EMPTY or the target whole-disk is absent from it (previously: empty-but-successful lsblk ⇒ unclaimed). Placed after the member/mount loop; the gatherErr/wholeDiskOK/isSystem/readonly/lvmPV/zfsMember ordering untouched.
D3 blank-format anti-retarget internal/localapi/wipe_reresolve.go (shared antiRetargetResolveExpect core + new antiRetargetResolveBlank), disks.go, formatjob.go, server.go (new reresolveBlank seam) The blank branch now derives the device's durable id (no durable id ⇒ 409 refuse — path-only formats not permitted), re-resolves it (resolve → re-derive exact match → re-inspect STILL !DataBearing) and formats the RE-RESOLVED device, never req.Device. The format-job record carries blank; restart recovery re-checks blank jobs with the blank variant. Confirmed (data-bearing) branch untouched.

Not in this batch (per spec §0/§12): A1 (stale-lock pool-membership — a SPIKE: the role lacks Pool.Audit, no cheap membership read exists); C1, C2, A2, B2B5, E1, E2 (deferred). B2's .. guard intentionally skipped — DriveNameFromRaw already rejects /, so .. alone can't traverse.

3. Tests + red-proofs (each new test shown FAILING on the pre-fix impl, then green on the fix)

Fix Tests Red-proof result
B1 TestInstallSnippet_RandomTempName, TestInstallSharedParent_RandomTempName (fake runner records the install source; random pattern + two-calls-differ + content + cleanup asserted) Pre-fix (HEAD files restored): both FAIL — fixed name, identical across calls. Fix restored → green.
D2 TestClassifyClaim_EmptyNodesRefused, TestClassifyClaim_TargetAbsentFromTree Pre-fix claim.go: both FAIL — (true,"unclaimed") returned. Fix restored → green.
D3 Pure: TestFormatBlankPath_AntiRetarget_{ReassignedDataBearingRefused,ReassignedDifferentDiskRefused,UnresolvableRefused,SameBlankProceeds}. Handler: TestFormat_Blank_{FormatsReresolvedDeviceNotCallerPath,ReresolveRefusalNoMkfs,NoDurableIDRefused} Pre-fix blank branch (formats req.Device, no binding): all 3 handler tests FAIL — mkfs ran on the caller path with HTTP 200 and no durable binding. Fix restored → green.
D1 scripts/mkfs-guarded-harness.sh on felhom-pve (root): throwaway loop devices (losetup, one -r read-only), PATH-shimmed lsblk FSTYPE fixture, /dev/null bound over pvs for the pvs-absent case, and a RECORDER bind-mounted over /usr/sbin/mkfs.ext4 in a private mount ns — no real mkfs possible; "formats" is proven by the recorder argv, refusal by its absence Fixed wrapper: 8/8 PASS (5 member FSTYPEs refused, read-only refused, pvs-hidden LVM still refused, plain blank disk still reaches mkfs). Pre-fix wrapper: 7/8 FAIL — every member/RO/pvs-hidden fixture reached mkfs. The loop-device -b check ran for real (no stub).

Full gates green on Windows AND on the build server (go1.26.0): go build ./... && go vet ./... && go test ./...0 failures. Test count 472 → 483 (+11).

4. Build / publish / deploy / verify

  • Built on 180 from 3f382bf: CGO_ENABLED=0 go build -ldflags "-X main.version=0.61.0"; --versionfelhom-agent 0.61.0.
  • Published to Gitea (generic package, delete-then-put + round-trip GET verified): AGENT_VERSION=0.61.0, AGENT_SHA256=d5f2f283501dac9daac1c197c9665c98f4577e11d155f5da831b26048c736ed8. Credentials sourced out-of-band (/root/.golden-secrets.env on the host; not committed anywhere).
  • Deployed to felhom-pve (backups kept: felhom-agent.bak-0.60.0, felhom-mkfs-guarded.bak-0.60.0): binary → /usr/local/bin/felhom-agent; new wrapper/usr/local/sbin/felhom-mkfs-guarded (grep-confirmed the D1 checks in the installed copy); new sudoers/etc/sudoers.d/felhom-agent (visudo -cf passed both staged and installed).
  • Verify after restart: service active, --version 0.61.0, 0 ERROR lines in the startup journal; capability self-check 46/46 ok, 0 degraded (proves the new sudoers globs cover the new manifest vectors live); ReassertGuestBinds/reconcile re-bound exactly the three enrolled drives (felhom-flash, felhom-usb, teszt_enroll) — nothing else; stale-lock recovery ran with nothing to reap (no stale locks, no false action); local API listening; hub desired-state pulled.
  • B1 live-proven end-to-end: made the installed shared-parent script stale (appended a marker) → the next reconcile self-healed it through the REAL sudo path with random-named temps (install -m 0755 -- /tmp/felhom-shared-parent-281383295.sh …, install -m 0644 -- /tmp/felhom-shared-parent-2841067761.service …) — authorized by the new globs.
  • Cleanup: all /tmp staging removed from felhom-pve and 180; harness loop devices/workdir self-cleaned.

5. NOT yet live-validated — awaiting a supervised step

  • D1 destructive proof: a real mkfs attempt against a crafted ZFS/mdraid/LUKS/swap member disk.
  • D3 race proof: a live /dev re-enumeration race during a real format. Both are proven in unit-land + the recorder harness only, per the task's hard STOP.

6. Observations / follow-ups (recorded, not acted on)

  • Operator follow-up: the hub Day-0 artifact manifest still vouches agent 0.60.0 — record 0.61.0 / sha d5f2f283… in the password-gated operator UI (Configs → Day-0 artifacts) so Day-0 self-installs fetch the new build. (CC cannot do this; see hub-artifact-manifest gotcha.)
  • A1 (LOW, pending SPIKE): stale-lock recovery reaps by a pool-blind ListLXC scan — contained by the pool-scoped token; MUST be fixed before any broad/root-token deployment. Open design question: which pool-membership read the role can authorize (Pool.Audit absent today).
  • Deferred audit items: C1 (controller-swap version floor), C2 (NAS server allowlist), A2 (gate journal cross-check), B3/B4/B5 housekeeping, E1/E2 comment fixes.
  • Known pre-existing: the non-root agent cannot read the PBS key (felhom-pbs.pw permission denied at startup — the BUNDLE leftover, unchanged by this batch).