v0.61.0: audit fixes B1 (random temp staging) + D1 (mkfs wrapper member/RO re-checks) + D2 (empty-lsblk fail-safe) + D3 (blank-format anti-retarget)

From AUDIT-blast-radius-hostroot-localapi-2026-07-02.md. Each fix ships with a
non-hollow test + a companion red-proof (shown failing on the pre-fix impl).
Sudoers install-source grants became globs — deploy the sudoers drop-in with
the binary. A1 (stale-lock pool-membership) deliberately excluded (spike).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-03 07:25:50 +02:00
parent cc93dae792
commit 3f382bf762
20 changed files with 767 additions and 44 deletions
+47
View File
@@ -1,3 +1,50 @@
## v0.61.0 — blast-radius audit fixes B1 + D1 + D2 + D3 (2026-07-03)
Four LOW/INFO fixes from `AUDIT-blast-radius-hostroot-localapi-2026-07-02.md` — the "second gate must
mirror the first" batch. Each shipped with a non-hollow test AND a companion red-proof (test shown
failing on the pre-fix implementation). A1 (stale-lock pool-membership) is deliberately NOT here — it
needs a pool-read spike (the role lacks `Pool.Audit`); C1/C2/A2/B2B5/E1/E2 deferred.
- **B1 (LOW) — random temp staging for root-installed scripts.** `internal/guesthook/install.go`
(`InstallSnippet`) and `internal/localapi/intermediary.go` (`installSharedParentUnit`, new shared
`stageTemp`) staged root-executed scripts through FIXED, predictable `/tmp` names via `os.WriteFile`
(no O_EXCL, follows symlinks) — a local TOCTOU into a root-run PVE hookscript / boot script. Both now
use the `os.CreateTemp` random-name pattern `lanresolver` already used. `configs/felhom-agent.sudoers`
(`FELHOM_GUESTHOOK`/`FELHOM_INTERMEDIARY`) install-SOURCE grants became globs
(`/tmp/felhom-guest-hook-*.sh`, `/tmp/felhom-shared-parent-*.{sh,service}`; destinations stay pinned);
`internal/capability/manifest.go` representative vectors updated to match. Tests:
`TestInstallSnippet_RandomTempName`, `TestInstallSharedParent_RandomTempName` (fake runner records the
install source: random pattern, two calls differ, content + cleanup asserted).
- **D1 (LOW) — the guarded mkfs wrapper now mirrors the classifier's member/RO classes.**
`configs/felhom-mkfs-guarded.sh` re-checked only system-disk / LVM-PV (PATH-dependent `command -v
pvs`) / foreign-mount — a bypassed agent could mkfs a ZFS/mdraid/LUKS/swap member or a read-only
disk. Added (additive; nothing removed/reordered): `/sys/block/<disk>/ro` == 1 → die; an
lsblk-FSTYPE loop over the whole disk refusing exactly `claim.go`'s `memberFSTypes`
(LVM2_member/zfs_member/linux_raid_member/crypto_LUKS/swap — the FSTYPE catch works with pvs
absent); pvs resolved via absolute candidates (/usr/sbin/pvs, /sbin/pvs). Validated by the new
`scripts/mkfs-guarded-harness.sh` on felhom-pve: throwaway loop devices + PATH-shimmed lsblk + a
recorder bind-mounted over mkfs.ext4 in a private mount namespace (no real mkfs possible) — fixed
wrapper 8/8 (incl. plain-blank-disk still formats); pre-fix wrapper red-proof: 7/8 hostile fixtures
reached mkfs.
- **D2 (INFO) — `classifyClaim` empty-lsblk fail-safe.** A successful-but-empty `lsblk`
(`{"blockdevices":[]}`) skipped the member/mount loop and returned `unclaimed`.
`internal/storage/claim.go` now refuses when the node tree is empty OR the target whole-disk is
absent from it (undeterminable topology ⇒ claimed). Tests: `TestClassifyClaim_EmptyNodesRefused`,
`TestClassifyClaim_TargetAbsentFromTree`.
- **D3 (INFO) — blank-format anti-retarget (AGENT-001's benign-branch twin).** `handleDiskFormat`'s
blank branch formatted the mutable caller-supplied `req.Device` with no durable-id binding — a /dev
re-enumeration between inspect and mkfs could format a data-bearing disk that inherited the node.
The blank branch (`internal/localapi/disks.go`) now derives the device's durable id (no durable id ⇒
409 refuse — path-only formats are not permitted), re-resolves it via the new
`antiRetargetResolveBlank` (`wipe_reresolve.go`: shared `antiRetargetResolveExpect` core; the blank
variant asserts the device is STILL !DataBearing), and formats the RE-RESOLVED device. The format
job record (`formatjob.go`) carries `blank`; restart recovery re-checks blank jobs with the blank
variant (durable-id-bound, fail-safe refuse). Confirmed/data-bearing branch untouched. Tests:
`TestFormatBlankPath_AntiRetarget_{ReassignedDataBearingRefused,ReassignedDifferentDiskRefused,UnresolvableRefused,SameBlankProceeds}`,
`TestFormat_Blank_{FormatsReresolvedDeviceNotCallerPath,ReresolveRefusalNoMkfs,NoDurableIDRefused}`.
- Deploy note: the host's `/etc/sudoers.d/felhom-agent` MUST be updated together with the v0.61.0
binary (the old fixed-name grants deny the new random-name installs, and vice versa).
## v0.60.0 — proof-of-launch destroy gating + restore-test band-advance (campaign F1/F2) (2026-07-02)
Fixes the pool-effects campaign's HIGH finding (F1, `CAMPAIGN-pool-effects-2026-07-01.md`): the bring-up