fix(daemon): autounlock walks config.allKeys, not prisma.key (#16)
Some checks failed
Docker image / build-and-push-image (push) Has been cancelled

The first cut of `maybeAutounlock` enumerated `prisma.key` based on
the design issue's pseudocode. Empirically that's the wrong source:
the Prisma `Key` table is only populated by the NIP-05
`create_account` path, which stores keys *plain-at-rest* in
`nsecbunker.json` (no encryption involved). The `create_new_key`
flow that lnbits's `RemoteBunkerSigner` uses provisions encrypted
`{iv, data}` blobs directly into the JSON `keys` map without
touching the Prisma table at all.

Result of the v1 enumeration on regtest:

  🔓 autounlock: enabled (source=NSEC_BUNKER_AUTOUNLOCK_PASSPHRASE),
     unlocked 0/0 keys in 0ms

…despite 67 encrypted blobs sitting in nsecbunker.json. The Prisma
table was empty because none of the regtest keys came from
`create_account`. Greg's key would have been a no-op even with the
autounlock env set; the manual `unlock_key` admin RPC would still
have been required.

Fix: enumerate `this.config.allKeys` (the in-memory snapshot of
`nsecbunker.json`'s `keys` map, populated at daemon-fork time per
`src/commands/start.ts:144`) filtered to entries with the `iv`+`data`
shape. That's the canonical "what's encrypted at rest" set —
exactly the rows for which manual `unlock_key` was previously
required per restart.

Plain-key entries (`{key: ...}` from `create_account`) are skipped
here for log clarity — they were already loaded by `startKeys`'
second pass and live in `activeKeys`; `unlockKey`'s post-#16
idempotency guard would no-op them anyway, but emitting "unlocked"
log lines for keys that didn't need unlocking is noise.

Updates `docs/AUTOUNLOCK.md` accordingly so the description matches
the implementation.

Refs aiolabs/nsecbunkerd#16.
This commit is contained in:
Padreug 2026-05-31 15:34:51 +02:00
commit 030d3cea0f
2 changed files with 35 additions and 12 deletions

View file

@ -35,11 +35,14 @@ the autounlock pass runs:
1. Read the passphrase from the configured source. Failure to read
(missing file, no permission) is fatal at boot.
2. Enumerate every row in the `Key` Prisma table where
`deletedAt IS NULL`.
3. For each row, call `unlockKey(keyName, passphrase)`. `unlockKey`
is idempotent post-#16: if the key was already unlocked by a
prior pass, it's a no-op.
2. Enumerate the encrypted-at-rest entries in `nsecbunker.json`'s
`keys` map — entries carrying the `{iv, data}` shape from
`create_new_key`. Plain-key entries (`{key: ...}` shape from
`create_account`) are already loaded by the existing
`startKeys()` passes and are skipped here for log clarity.
3. For each candidate, call `unlockKey(keyName, passphrase)`.
`unlockKey` is idempotent post-#16: if the key was already
unlocked by a prior pass, it's a no-op.
4. Log per-key INFO on success, WARN on `unlockKey → false`
(typically: wrong passphrase, possibly the key was created under a
historical passphrase that differs from the current one), ERROR on