12 KiB
phase, plan, subsystem, status, tags, requires, provides, affects, tech-stack, key-files, decisions, metrics
| phase | plan | subsystem | status | tags | requires | provides | affects | tech-stack | key-files | decisions | metrics | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| quick-260731-upz | 01 | security | complete |
|
|
|
|
|
|
|
Quick Task 260731-upz: Entropy/Seed Audit + PSBT Signing Architecture — Summary
Turned the confirmed 2026-07-30 Coinkite COLDCARD low-entropy incident into an evidence-backed audit of Archipelago's own entropy paths, a plannable PSBT-first signing spec, a prioritised remediation backlog wired into the tracker, and one small, test-proven hardening fix to master-seed generation.
⚠️ Push deliberately withheld
Nothing was pushed. Per explicit instruction, the core/archipelago/src/seed.rs diff is
held for human review before it leaves this machine — it is master-seed generation code for
every new node.
Review commands:
git show 8b51b7e2 # the full seed.rs diff (122 insertions, 2 deletions)
git log --oneline -4 # this plan's four commits
git show --stat 8b51b7e2
Commits awaiting review, all on main, none pushed:
| Commit | Type | Contents |
|---|---|---|
f11db4ea |
docs | docs/security/ENTROPY-SEED-AUDIT-2026-07-31.md (the audit) |
5faf1a3c |
docs | docs/security/PSBT-SIGNING-ARCHITECTURE.md (the spec) |
5ba80e49 |
docs | Remediation backlog + F-13 + tracker items |
8b51b7e2 |
fix | core/archipelago/src/seed.rs — the diff to review |
What to check in 8b51b7e2: that the change is limited to (a) routing mnemonic
generation through a helper that takes its RNG as a parameter, with OsRng passed at the
production call site, and (b) two new tests — and that no derivation path, word count,
BIP-39 passphrase decision, or at-rest encryption behaviour changed. It does not, but that
is the thing worth confirming with your own eyes.
Headline result
No Coldcard-class entropy defect exists in this codebase. Every first-party
key-generation call site draws from a genuine CSPRNG. There is no Mersenne Twister, no
clock-seeded key, no SmallRng, no seed_from_u64, and no Math.random() in any browser
key path. The code also does several things better than most implementations (audit §5).
But the audit found something more urgent than anything entropy-related.
The Critical finding (F-01) — not what we went looking for
seed.generate and seed.restore are in UNAUTHENTICATED_METHODS
(core/archipelago/src/api/rpc/middleware.rs:24-28), which skips session, RBAC and CSRF.
Neither handler checks whether onboarding is already complete, and
NodeIdentity::from_seed (core/archipelago/src/identity.rs:79-114) overwrites node_key,
nostr_secret and the FIPS mesh key unconditionally. There is no rate limit. The
endpoint is proxied to the LAN over plaintext HTTP
(image-recipe/configs/nginx-archipelago.conf:11, :165, :192) and mesh peers can reach
it too (core/archipelago/src/server.rs:2080).
One unauthenticated POST can take over or destroy a live node's identity, and
seed.restore lets the attacker choose the mnemonic. The guard already exists and is simply
never called — NodeIdentity::key_exists (identity.rs:117).
Surfaced by tracing secret classes (3) and (4) end-to-end rather than only checking where their bits come from. Queued as backlog R-01 and as a Tier 2 tracker item; it changes an authentication boundary on a live fleet and needs its own phase.
ARCHY findings — adjudicated
| Tag | Verdict | Note |
|---|---|---|
| [ARCHY-1] | CONFIRMED | seed.rs:92 → bip39-2.1.0/src/lib.rs:311-313 → :296-298 (&mut rand::thread_rng()) → :267-283. FIXED. |
| [ARCHY-2] | CONFIRMED (positive) | The GRND_NONBLOCK probe is used as a probe only; its byte is discarded; no key material comes from it. Better than most. |
| [ARCHY-3] | PARTIALLY CONFIRMED | The feared version does not exist. Three of four sub-questions answered from the tree; the rest is an UNVERIFIED on-node checklist. |
| [ARCHY-4] | CONFIRMED, and worse | Every claim checks out, plus it is an integrity/availability exposure too — that is F-01. |
| [ARCHY-5] | REFUTED as a present defect | totp.rs:305's charset is 32 chars and 32 divides 256, so bias is zero today. Latent, not live. Stated plainly rather than dropped. |
| Open Q9 | DIVERGENCE CONFIRMED | Argon2::default() = 19 MiB / t=2 / p=1; ADR-005 says 64 MB / 3. |
Two findings the research did not predict
- F-03 (High) — the installed rootfs is a cached container export shared by every
node, baking SSH host keys and a TLS keypair. Per-device regeneration exists and is
correct in intent, but both branches are fail-open and
touch "$MARKER"runs unconditionally (image-recipe/_archived/build-auto-installer-iso.sh:1647,:1659,:1663), so one transient failure permanently leaves that node on the image-wide shared keys, visible only in a log file. - F-13 (High) —
bitcoin.rs:203passesdisable_private_keys=falseand:229-231importswpkh(xprv/...), so the BIP-84 account private key is persisted in Bitcoin Core'swallet.dat(with an empty wallet passphrase) in addition to the Argon2 envelope. The descriptors also carry no key-origin annotation, so no hardware signer could use them.
Scoping correction worth carrying forward
image-recipe/_archived/ is not dead code. image-recipe/build-debian-iso.sh:19-40
copies _archived/build-auto-installer-iso.sh to a temp path, rewrites its relative paths,
and execs it. The "archived" auto-installer is the live ISO builder. The plan scoped it
out; treating it as dead would have made [ARCHY-3] unanswerable and hidden F-03 entirely.
Deliverables
docs/security/ENTROPY-SEED-AUDIT-2026-07-31.md — 13 findings, each with severity,
file:line evidence, exploitability, blast radius and concrete remediation; all five ARCHY
tags adjudicated; all six mandated secret classes traced; a 13-item "What we do right"
section; a 7-item UNVERIFIED on-node checklist with paste-ready commands; and an R-00…R-15
remediation backlog. 103 file:line evidence references (gate required ≥20).
docs/security/PSBT-SIGNING-ARCHITECTURE.md — watch-only descriptor wallets, the full
Core RPC loop with wallet- vs node-scoped RPCs, analyzepsbt-driven UI state, Tier 1
single-sig and Tier 2 wsh(sortedmulti) on BIP-48, BC-UR v2 vs BBQr vs file transport, the
honest LND capability matrix, the hot wallet as an explicitly-secondary tier, migration
guidance, and a 7-phase rollout with dependencies and candidate requirements. Cross-links
and answers two open items in docs/hardware-signer-design.md.
docs/UNIFIED-TASK-TRACKER.md — 9 new items in the file's existing tier/checkbox format:
4 in Tier 0, 3 in Tier 1, 4 in Tier 2 (including the Critical F-01 item and PSBT Phase 1).
The one code change
core/archipelago/src/seed.rs — generate_mnemonic_with<R: CryptoRng + RngCore> calls
bip39's injectable generate_in_with; MasterSeed::generate() passes OsRng explicitly.
mnemonic_generation_uses_injected_rng asserts the result equals
bip39::Mnemonic::from_entropy(<the exact bytes the test RNG emitted>) — the direct proof
that the injected RNG, not bip39's transitive default, is the one consumed — plus a
known-answer pin and a determinism check. This test cannot be written against the previous
code, because Mnemonic::generate(24) exposes no seam.
Verified: CARGO_INCREMENTAL=0 cargo test -p archipelago seed:: → 25 passed, 0
failed (23 pre-existing + 2 new).
Honest limitation, recorded in the audit: this removes a future failure mode. It does
not retroactively change seeds generated before it, which came from rand::thread_rng() —
a genuine CSPRNG, so nothing is weakened, but their guarantee rests on rand 0.8.5's
behaviour rather than on this call site.
Deviations from plan
image-recipe/_archived/brought into scope (plan said excluded). Justified above; documented in the audit's §1 so the next auditor does not re-derive it.- F-13 added to the audit during Task 3. Discovered while reading
bitcoin.rsfor the PSBT spec. It belongs to secret class (1), which Task 1 was required to trace, so it was written up rather than left in the spec alone. - R-12 (
totp.rsmodulo bias) NOT applied, though the plan permitted it.[ARCHY-5]was refuted as a present defect — 32 divides 256, so there is no bias today. Changing working crypto code for a latent-only issue did not meet the plan's "small and obviously correct" bar during a security-sensitive pass. Queued as R-12. cargo auditnot run —cargo-auditis not installed. Recorded as gap F-07 with CI remediation R-05 rather than silently skipped.
Not done, deliberately
- No push, no tag, no deploy (see the banner above).
- No PSBT/watch-only/multisig implementation — the spec is a spec.
core/archipelago/src/container/secrets.rsuntouched (backlog R-13) — it carried another agent's uncommitted work. Read-only for the audit, as required.
Concurrent-agent hygiene
All four commits verified against the forbidden-path list: no commit authored by this plan
contains any of the other agents' files. Every commit staged by explicit path; no
git add -A, no git add ., no git commit -a. The submodule guard (indeedhub) ran
before each commit and passed. Their uncommitted work (ScreensaverRing.vue,
SendBitcoinModal.vue, WalletScanModal.vue, and the earlier set) is intact.
Self-Check: PASSED
docs/security/ENTROPY-SEED-AUDIT-2026-07-31.md— FOUNDdocs/security/PSBT-SIGNING-ARCHITECTURE.md— FOUNDcore/archipelago/src/seed.rs— modified, tests green- Commits
f11db4ea,5faf1a3c,5ba80e49,8b51b7e2— all FOUND ingit log - Task 1 verify gate — OK (103 evidence refs, all required tokens present, no secret-shaped strings)
- Task 2 verify gate — OK (all 12 required tokens present, no secret-shaped strings)
- Task 3 verify gate — OK (backlog present, both tracker links present, no forbidden paths in any of the four commits)
- No real secret value appears in any produced document — verified by pattern scan on both.