checkUnlockerExists fails open: an unenumerable unlockers.d reports "no duplicate" and permits a duplicate unlocker #51
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Surfaced during the re-review of PR #29 (#29 (comment)). Pre-existing on
main, not a regression from that PR — the reviewer confirmedmainfails open in the same three places, which is why it did not block the merge.Problem
internal/cli/unlockers.go:786,:795,:799-807.checkUnlockerExistsis the duplicate-detection gate run before creating a new unlocker. At three separate points it treats a failure to enumerateunlockers.das "no duplicate found" and returns success, rather than as an error.So whenever the directory cannot be read — a transient I/O error, a permissions problem, a partially-created unlocker directory of the kind #48 describes, a filesystem hiccup — the gate silently reports the coast is clear and creation proceeds. The user ends up with two unlockers where the tool's own invariant says there should be one.
The failure direction is what makes this worth fixing rather than tolerating. A check that fails closed refuses to create an unlocker and shows an error the user can act on. This one fails open: it creates state that the duplicate check exists specifically to prevent, and does so silently. In a tool where unlockers are the access-control layer for a vault's long-term key, "we could not verify, so we allowed it" is the wrong default.
Related but distinct from #48, which is about
CreatePGPUnlockerleaving a partial directory behind on failure. The two compound: #48 creates the unreadable directory, and this issue then fails to notice it when deciding whether a duplicate exists.Definition of done
unlockers.dbecomes an error that aborts unlocker creation, rather than being reported as "no duplicate".checkUnlockerExistsare fixed, not just the first one found.unlockers.dcauses unlocker creation to fail rather than succeed. Assert on the resulting state, not just the return value: no new unlocker directory should exist afterward.make checkgreen.TODO.mdupdated in the same commit.Implementation requirements
findUnlockerIDByMetadatato skip unreadable entries with a warning, which is correct for listing. This issue is the opposite case: for a safety gate, skipping is not acceptable, because skipping is what produces the fail-open. Make sure the fix here does not get "harmonized" with the listing behavior — the two call sites genuinely want different things, and that difference should be commented so a future reader does not unify them.