handleDiskFormat's customer-confirmed branch formatted the mutable req.Device path; the durable id only bound the confirmation, never the mkfs target. A /dev reassignment between inspect and mkfs could wipe the wrong physical disk. Now mirrors signedjobs.WipeExecutor: resolve confirmed durable id -> current device, re-derive + require exact match, re-inspect (still data-bearing), then format THAT device. Any refusal -> 409, no mkfs. New antiRetargetResolve helper (injected deps, unit-tested: mismatch/gone/blank/empty all refuse). Injectable reresolveWipe seam on Server (defaults to real storage funcs). BRANCH ONLY — pending supervised review/deploy (see AGENT-001-FIX-NOTES.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4.1 KiB
AGENT-001 fix — inline customer-confirmed wipe: anti-retarget re-resolution
Branch: fix/agent-001-wipe-durable-reresolve (off main @ d17b5ab) — PENDING REVIEW, NOT deployed.
Status: branch-only. No agent build, no deploy, no golden rebake, no re-provision. That is reserved for a supervised session (this fix touches the destructive storage-wipe path).
The bug
internal/localapi/disks.go handleDiskFormat, the data-bearing customer-confirmed branch
(dec.Allowed), inspected and gate-bound the device by its durable id but then called
s.disks.Format(ctx, req.Device, …) on the caller-supplied mutable /dev path. The durable id
only bound the confirmation; it was never re-resolved to the device just before mkfs. If a USB
re-enumeration reassigned /dev/sdbN to a different physical disk between inspection and mkfs
(the classify→mkfs TOCTOU), the wipe hit the wrong drive — data loss on an unintended disk.
The signed-jobs path (internal/signedjobs/wipe.go WipeExecutor.Execute) already does this
correctly: resolve durable→device, re-derive the device's durable id and require an exact match,
re-inspect, then format the re-resolved device. The inline path did not.
What changed
- New
internal/localapi/wipe_reresolve.go:antiRetargetResolve(durableID, resolve, derive, inspect)— pure, injected-deps helper mirroringWipeExecutor: refuse empty durable id; resolve→current device; re-derive and require exact match (a/devnode now pointing at a different disk derives a different id → refuse); re-inspect and require still-DataBearing(). Returns the re-resolved device to format.(*Server).reresolveDurableForWipewires it with the realstorage.ResolveDurableDevice/storage.DeviceDurableID/s.disks.InspectDevice.
internal/localapi/disks.go: thedec.Allowedbranch now callss.reresolveWipe(ctx, deviceDurable)and formats the returned device (neverreq.Device). On any refusal it returns409 Conflictwith the reason and does not callmkfs. The response/log now reference the re-resolved device.internal/localapi/server.go: new injectable fieldreresolveWipe, defaulted inNewServertos.reresolveDurableForWipe. Production behaviour unchanged (same real functions); the seam exists so the inline path is unit-testable without real/dev/disk/by-*.- Tests:
wipe_reresolve_test.gocoversantiRetargetResolvedirectly — happy path, empty durable, no-longer-resolves, durable-id mismatch (the core retarget case), no-longer-data-bearing, re-inspect error. The existing integration testTestFormat_DataBearing_UserDataConfirmed_FormatsstubsreresolveWipe(the test helper sets it). Fullgo build/vet/test ./...green at the branch HEAD.
Why it can't break legitimate wipes
storage.DeviceDurableID produces byid:/byuuid: ids and storage.ResolveDurableDevice accepts
exactly those — they round-trip. A normal user-data drive resolves back to itself, re-derives the same
id, and inspects data-bearing → format proceeds. Only three new refusals occur, all correct:
durable id gone, durable-id mismatch (retarget), or no-longer-data-bearing.
Review checklist for the supervised merge+deploy+golden-rebake session
- Confirm
antiRetargetResolverefuses on mismatch/gone/blank/empty (rungo test ./internal/localapi/ -run AntiRetarget -v). - Sanity-check
DeviceDurableID↔ResolveDurableDevicescheme round-trip on the real demo USB (felhom-usb) so a genuine customer wipe still resolves+matches (read-onlyDeviceDurableIDon the live device; do NOT wipe). - Confirm the
409 Conflictrefusal surfaces sensibly in the controller UI (controlleragentapimaps the format response — verify it doesn't treat 409 as a hard error that hides the reason). - Bump agent version + CHANGELOG, build, golden-rebake, re-provision per the agent deploy runbook.
- Live-validate one happy customer-confirmed wipe on a demo scratch drive (re-resolve matches → mkfs) and confirm the audit log line shows the re-resolved device == the confirmed durable id.