v0.5.0 — persist the prompted passphrase; npm-script parity for the ceremonies
Security
-
Trail-of-Bits pass on the seed-binding work — 8 findings fixed before release. Two independent adversarial reviews (crypto/format + install/docs/reveal) ran against this batch; the crypto core survived every malleability and payload-edge-case attack, but the rollout had holes: (1)
references/agent-class.md— the "drop-in" class users actually copy — still shipped the pre-seal env-only resolver, so someone who sealed a budget and followed the doc got no budget at all; (2) a sealed seed whose policy didn't reach the agent ran silently unbound — now the plaintext version byte is checked and a sealed-but-unbound wallet throws; the context also moved toglobalThis(duplicate module copies) and a policy-less load can no longer clear an established one; (3) the seed header wasn't in the GCM AAD, so flipping0x02→0x01passed authentication and reinterpreted the payload as a bare mnemonic — v2 now authenticates the header (v1 untouched); (4)npx <cmd>does not fail closed — it fetches a registry package named after the command, and all five of our bin names were unregistered; docs now usenpm exec --no, and both agent-facing rule files say to refuse any npx install offer; (5) the ceremonies read the passphrase from.env(which sits next to the wallet), reducing "requires the passphrase" to "requires a PTY" — they now always prompt; (6) the over-budget error told sealed-policy callers to edit.env, the exact move the seal neutralizes; (7)SIGTERM/SIGHUPskipped the seed-reveal screen wipe (theexithook doesn't run for signals), leaving the mnemonic on screen indefinitely; (8) the reveal claimed "alternate buffer wiped" unconditionally, but GNU screen shipsaltscreen off— the message is now conditional and tells screen users to clear their scrollback. Also: README no longer teaches an unpinned SDK install, the pin-staleness tradeoff is stated, andnpm@lateston Node 20 is corrected (engines are warn-only, so it installs npm 12 anyway — usenpm@11). +12 regression tests. -
Seed-bound spending policy (opt-in): the budget can now be sealed inside the encrypted seed. Guard enforcement used to depend on files the agent can write —
rm ~/.spark/spend-ledger.json(or truncating it, or editing.envto dropSPARK_DAILY_BUDGET_SATS) silently restored the full budget; a cleanup command, a prompt injection, or a machine migration all failed open. Nownpx sparkbtcbot-set-policy(user-run, TTY-gated) bindsdailyBudgetSatsinto the encrypted seed payload (v2 seed format — reading it needs the passphrase, tampering fails the GCM tag, deleting it deletes the wallet: the agent can't remove the budget without removing the money), and the spend ledger becomes HMAC-signed with a seed-derived HKDF key (never the AES key). Under a bound policy, a missing / unsigned / edited ledger fails closed with instructions to runnpx sparkbtcbot-reset-ledger— the passphrase-gated legitimate reset that finally makes reset distinguishable from attack (previouslyrmwas both). A seed-bound budget wins over the env var absolutely. v1 seeds and env-var budgets are completely unchanged — no policy, no new behavior. Honest limits, documented: the bar moves fromrmto executing code, not to impossibility; replay of a validly-signed old ledger is undefendable client-side; raw-SDK calls still bypass wrapper guards. New exports:loadSeedPayload(FromEnv),validateSeedPolicy,deriveLedgerHmacKey,initSignedLedger; new CLIs:sparkbtcbot-set-policy,sparkbtcbot-reset-ledger. 16 attack-first tests (each attack proven to throw).
Changed
- Agents are explicitly told that running SETUP is allowed and expected. Live testing showed agents over-extending the reveal-mnemonic prohibition into refusing
npm run setupwhen the user asked — citing rules the docs never contained (setup has not printed or written the mnemonic in plaintext since 0.4.0). AGENTS.md and SKILL.md now carry an affirmative rule: creating the wallet on the user's behalf is the designed flow; the only secret to guard during setup is the passphrase, which goes to.envand is never echoed.