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
+20 -10
View File
@@ -17,9 +17,10 @@ import (
type formatJob struct {
JobID string `json:"job_id"`
Device string `json:"device"`
DurableID string `json:"durable_id"` // "" for a blank (benign) format — never auto-recovered
DurableID string `json:"durable_id"` // durable-id binding; "" only in legacy records (never auto-recovered)
FSType string `json:"fstype"`
Phase string `json:"phase"` // running | done | failed
Blank bool `json:"blank,omitempty"` // audit D3: blank (benign) format — recovery re-checks STILL-blank, not data-bearing
Phase string `json:"phase"` // running | done | failed
Error string `json:"error,omitempty"`
StartedAt string `json:"started_at"`
UpdatedAt string `json:"updated_at"`
@@ -93,15 +94,16 @@ func (s *FormatJobStore) save(j *formatJob) error {
// request context), with a long bound. It returns a channel that yields the format error (nil on
// success). The caller may stop waiting (client disconnect) without killing the mkfs — the goroutine
// runs to completion and records the outcome. device is the ALREADY anti-retarget-resolved device; the
// record carries durableID so a restart can re-resolve + re-run.
func (s *Server) startFormatDetached(device, durableID, fstype string) <-chan error {
// record carries durableID so a restart can re-resolve + re-run. blank marks a benign (blank-device)
// format, so restart recovery re-checks STILL-blank rather than data-bearing (audit D3).
func (s *Server) startFormatDetached(device, durableID, fstype string, blank bool) <-chan error {
base := s.baseCtx
if base == nil {
base = context.Background()
}
job := &formatJob{
JobID: s.nowFn().UTC().Format("20060102T150405Z"), Device: device, DurableID: durableID,
FSType: fstype, Phase: formatPhaseRunning,
FSType: fstype, Blank: blank, Phase: formatPhaseRunning,
StartedAt: s.nowFn().UTC().Format(time.RFC3339), UpdatedAt: s.nowFn().UTC().Format(time.RFC3339),
}
if s.formatJobs != nil {
@@ -151,18 +153,26 @@ func (s *Server) RecoverFormatJob(ctx context.Context) {
return
}
if job.DurableID == "" {
s.logger.Warn("format-job recover: interrupted blank format — marking failed (retry needed; not auto-re-running a path-bound format)", "device", job.Device)
s.logger.Warn("format-job recover: interrupted format has no durable id (legacy record) — marking failed (retry needed; not auto-re-running a path-bound format)", "device", job.Device)
s.finishFormatJob(job, fmt.Errorf("interrupted by agent restart; retry the format"))
return
}
device, err := s.reresolveWipe(ctx, job.DurableID)
// Audit D3: a blank format authorized "nothing to destroy" — its recovery re-check must assert
// STILL-blank (an interrupted mkfs may leave partial signatures; if the re-resolved device probes
// data-bearing the blank re-check refuses fail-safe and the caller retries). The customer-confirmed
// wipe path keeps the data-bearing re-check as before.
reresolve := s.reresolveWipe
if job.Blank {
reresolve = s.reresolveBlank
}
device, err := reresolve(ctx, job.DurableID)
if err != nil {
s.logger.Warn("format-job recover: durable-id no longer resolves — NOT re-formatting (anti-retarget)", "durable_id", job.DurableID, "err", err)
s.logger.Warn("format-job recover: durable-id did not re-resolve cleanly — NOT re-formatting (anti-retarget)", "durable_id", job.DurableID, "blank", job.Blank, "err", err)
s.finishFormatJob(job, fmt.Errorf("durable-id %s did not re-resolve after restart: %w", job.DurableID, err))
return
}
s.logger.Warn("format-job recover: re-running interrupted format detached", "durable_id", job.DurableID, "device", device, "fstype", job.FSType)
_ = s.startFormatDetached(device, job.DurableID, job.FSType) // detached; updates the record on completion
s.logger.Warn("format-job recover: re-running interrupted format detached", "durable_id", job.DurableID, "device", device, "fstype", job.FSType, "blank", job.Blank)
_ = s.startFormatDetached(device, job.DurableID, job.FSType, job.Blank) // detached; updates the record on completion
}
// nowFn returns the server clock (testable), defaulting to time.Now.