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:
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user