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
+18
View File
@@ -85,6 +85,24 @@ func classifyClaim(f claimFacts) (unclaimed bool, reason string) {
return false, "device is mounted at " + n.mountpoint + " (" + n.name + ")"
}
}
// Fail-safe backstop (audit D2): a successful-but-EMPTY lsblk (or a tree that does not even contain
// the target whole-disk) means the member/mount loop above inspected nothing — that is undeterminable
// topology, not proof of freedom. Without this, "unclaimed" rested on the untested assumption that
// lsblk always ERRORS (non-zero exit) on a bad device rather than emitting empty success.
if len(f.nodes) == 0 {
return false, "empty block topology (undeterminable) — refusing"
}
base := path.Base(f.wholeDisk)
found := false
for _, n := range f.nodes {
if n.name == base {
found = true
break
}
}
if !found {
return false, "target disk " + base + " absent from block topology (undeterminable) — refusing"
}
return true, "unclaimed"
}
+30
View File
@@ -64,6 +64,36 @@ func TestClassifyClaim(t *testing.T) {
}
}
// TestClassifyClaim_EmptyNodesRefused is the audit-D2 negative test: a successful-but-EMPTY lsblk
// (`{"blockdevices":[]}` → zero nodes) previously skipped the member/mount loop entirely and returned
// (true,"unclaimed") — the one hole in the "undeterminable ⇒ claimed" fail-safe. It must refuse.
func TestClassifyClaim_EmptyNodesRefused(t *testing.T) {
for _, nodes := range [][]claimNode{nil, {}} {
f := claimFacts{device: "/dev/sdd", wholeDisk: "/dev/sdd", wholeDiskOK: true, nodes: nodes}
unclaimed, reason := classifyClaim(f)
if unclaimed {
t.Fatalf("nodes=%v: empty topology classified UNCLAIMED (reason %q) — fail-safe hole", nodes, reason)
}
if !strings.Contains(reason, "empty block topology") {
t.Errorf("nodes=%v: reason %q missing the empty-topology explanation", nodes, reason)
}
}
}
// TestClassifyClaim_TargetAbsentFromTree (audit D2): lsblk returned SOME tree, but the target
// whole-disk is not in it — the loop inspected the wrong device's signals. Undeterminable → refuse.
func TestClassifyClaim_TargetAbsentFromTree(t *testing.T) {
f := claimFacts{device: "/dev/sdd", wholeDisk: "/dev/sdd", wholeDiskOK: true,
nodes: []claimNode{{name: "sdc"}, {name: "sdc1", fstype: "ntfs"}}} // benign signals, wrong disk
unclaimed, reason := classifyClaim(f)
if unclaimed {
t.Fatalf("target-absent tree classified UNCLAIMED (reason %q)", reason)
}
if !strings.Contains(reason, "absent from block topology") {
t.Errorf("reason %q missing the target-absent explanation", reason)
}
}
func TestParseLsblkNodes(t *testing.T) {
out := []byte(`{"blockdevices":[{"name":"sdd","fstype":null,"mountpoint":null,"children":[{"name":"sdd1","fstype":"ntfs","mountpoint":null}]}]}`)
nodes, err := parseLsblkNodes(out)