Commit Graph
5 Commits
Author SHA1 Message Date
Jeroen UbbinkandClaude Opus 4.8 1045f1061b fix(settings): derive npub from the vault nsec, not a stale env value
initialize() only filled npub when it was empty, so an existing node
with a stale NSEC still lingering in its env/blob kept that value's
derived npub even after the vault took ownership of a different nsec.
The node then held the vault's private key but announced the old env
key's public key — a split identity that anything reading settings.npub
would broadcast.

npub is a pure derivation of nsec and is never configured on its own, so
derive it from the live (vault) nsec and override rather than only fill.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 10:51:20 +02:00
Jeroen UbbinkandClaude Opus 4.8 0415806c5a fix(config): keep the vault the sole owner of node secrets
Two ways a stale legacy NSEC could override or resurrect an nsec the
vault already owns (issue #553):

- initialize() re-applied env/blob values onto live settings after
  bootstrap had decrypted the authoritative nsec, so a stale NSEC left
  in .env would clobber it on restart (e.g. after rotating the key in
  the admin UI). _apply_to_live_settings now never re-applies secret
  fields; bootstrap_secrets is their only writer.

- An empty encrypted_nsec could not distinguish "never migrated" from
  "intentionally cleared", so clearing the identity via the admin API
  and restarting re-imported the old NSEC from env/blob. Record vault
  ownership in a new secrets.nsec_managed column (set on legacy import
  and on every set_nsec write); bootstrap skips the legacy import once
  the vault owns the nsec, so a cleared identity stays cleared.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 10:51:20 +02:00
Jeroen UbbinkandClaude Opus 4.8 4872c318d5 fix(settings): keep npub consistent with a store-only nsec on initialize
When the nsec lives only in the encrypted Secret store (env carries no NSEC) and
the settings blob holds no npub, bootstrap_secrets decrypted the nsec and derived
the npub into memory, but SettingsService.initialize then re-derived settings
from the npub-less blob and overwrote the live npub back to empty — leaving a
private key with no matching public key, so the node silently stopped announcing
a usable Nostr identity.

Derive npub from the live nsec during initialize when the merged settings carry
none, so the public key stays consistent with the identity and is persisted to
the blob.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 10:51:20 +02:00
Jeroen UbbinkandClaude Opus 4.8 56a67c0a86 fix(settings): fail fast when an nsec is set but ROUTSTR_SECRET_KEY is missing
Encryption of the Nostr identity at rest is mandatory. When a legacy nsec is
present (env or blob) but no ROUTSTR_SECRET_KEY is set, bootstrap previously
fell into vault.encrypt and surfaced its generic "key not set" error. Raise an
explicit, nsec-contextual error first so the boot failure is intentional and
actionable — it names the missing key and prints the generation command —
rather than relying on vault throwing incidentally. No secret is dropped: the
node refuses to start until the operator sets the key.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 10:51:20 +02:00
Jeroen UbbinkandClaude Opus 4.8 c40723c1e2 feat(settings): bootstrap secrets at startup and require ROUTSTR_SECRET_KEY for stored nsec
Persist and load admin password and nsec from the encrypted Secret store on
boot: generate a temporary admin password on first run (logged once), encrypt
a provided nsec, and fail fast if a stored nsec cannot be decrypted with the
current key. Stop clobbering live secret settings with empty env values.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 10:50:51 +02:00