create_new_key clobbers an existing key — silent, unrecoverable identity loss on re-pair #39
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
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?
Severity: high (silent key destruction)
src/daemon/admin/commands/create_new_key.tsunconditionally generates a fresh keypair and callssaveEncrypted(...keyName), which overwritesconfig.keys[keyName]on disk. There is no guard for an already-existing key name.So invoking
create_new_keywith a name that already exists:loadNsecreplaces the in-memoryactiveKeys[keyName]entry (the old nsec then survives only in the still-running Backend, and is gone on the next restart).The old key is unrecoverable — no backup, no export RPC.
How it bit us
spirekeeper's
pair_spirere-pairs a machine through the samespire-<machine_id>keyName and documents the assumption verbatim (pairing.py):It isn't. A repeat
/pair(re-pair) for an already-paired machine rotated the machine's spire identity (437d8fd1…→ a freshly generated679ac2a8…), orphaning everything bound to the old key — connect tokens, the publishedcassettes-statebeacon, path-B wallet routing — with the old nsec lost. Reproduced on the dev stack 2026-06-21.Fix
PR: make
create_new_keyidempotent. If a key with the name already exists, recover and return it instead of generate-and-overwrite:_nsec→ decrypt the existing entry with the supplied passphrase, return its npub (the idempotent re-pair path);_nsec, unrecognized entry, or a passphrase that doesn't decrypt the existing key → throw rather than overwrite. Destroying a key must never be a silent side effect of "create".Follow-ups
acl/index.tstoimport type). Functionally verified for now; an ESM-capable test harness would let this land as a unit test.saveEncrypteditself should refuse to overwrite an existing name as defence-in-depth.Fixed via PR #40 (merged to
dev@4a0a3e7). Verified on the dev bunker: a repeat/pairfor an already-paired machine now returns the existing spire key with the on-disk entry unchanged (idempotent), where before it minted a new identity and clobbered the old blob. Closing.