docs(10): correct F-10's scope and add KEY-05 — crate-wide CSPRNG enforcement
The audit recorded F-10 as two call sites in container/secrets.rs. The real defaulted-RNG surface is 41 sites across 15 files: session.rs (16), pine_ha.rs (6), wallet/bdhke.rs (4 — ecash key material), mesh/x3dh.rs (2 — key-agreement material), storage_crypto.rs (1 — AEAD nonce), +10 more. Nothing is broken today: rand::random()/thread_rng() are ChaCha12 seeded from getrandom(2). What changes is blast radius — F-10's Low rating rested on 'per-app credentials rather than the master key hierarchy', which does not survive the true scope. Re-rated Medium as F-10a. Records why the original audit missed it: F-10 was reached by tracing the manifest-secrets path, and no step enumerated defaulted-RNG use across the crate independently of the traced paths. F-10's original text is left unedited so the correction is auditable rather than retroactive. R-13 superseded by R-16; tracker item replaced. Adds KEY-05 to Phase 10: sealed allowlist trait at key-gen seams, clippy disallowed-methods ban (compile-time, CI-enforced), cargo-deny on duplicate rand majors, degenerate-entropy runtime check, persisted CSPRNG-readiness verdict. Also retires the false 'impl CryptoRng for CountingRng' at seed.rs:656. Records the user's execution gate: Phase 10 does not start until the concurrent Phase 1 agent is finished and their changes are synced. KEY-05 is unplanned — the existing 5 plans predate it and a 6th is required. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f9b0659c4a
commit
5cf44c9a58
@@ -100,12 +100,26 @@ those are marked ✅ below with the commit that did it, so we stop re-litigating
|
||||
`Math.random()` picks a UX challenge, not key material, so the next auditor does
|
||||
not re-derive that it is benign.
|
||||
|
||||
- [ ] **Swap container `generated_secrets` to explicit `OsRng`** (entropy audit R-13,
|
||||
finding F-10) — `random_hex`/`random_base64` in
|
||||
`core/archipelago/src/container/secrets.rs:90-102` use `thread_rng()`. Correct on
|
||||
`rand 0.8.5`, but the same implicit-source pattern as [ARCHY-1]. Two-line change.
|
||||
**Currently blocked only on tree hygiene** — that file had another agent's
|
||||
uncommitted work when the audit ran.
|
||||
- [ ] ~~**Swap container `generated_secrets` to explicit `OsRng`** (entropy audit R-13,
|
||||
finding F-10) — two-line change in `container/secrets.rs:90-102`~~
|
||||
**SUPERSEDED 2026-08-02 by R-16 / KEY-05.** The audit scoped this at 2 call sites; the
|
||||
real surface is **41 across 15 files** — see the audit's new §F-10a. `secrets.rs` is 2
|
||||
of them, and a two-line fix there while 39 other sites inherit the same dependency
|
||||
default is not a fix.
|
||||
|
||||
- [ ] **Crate-wide CSPRNG enforcement — a defaulted RNG cannot be inherited anywhere**
|
||||
(entropy audit **R-16 / F-10a**, Medium) — tracked as **KEY-05 in Phase 10**, so plan
|
||||
and execute it there rather than as a standalone item. `session.rs` (16 sites),
|
||||
`pine_ha.rs` (6), `wallet/bdhke.rs` (4 — **ecash key material**), `mesh/x3dh.rs` (2 —
|
||||
**key-agreement material**), `storage_crypto.rs` (1 — **AEAD nonce**), +10 more files.
|
||||
Nothing is broken today (`rand::random()`/`thread_rng()` are ChaCha12 from
|
||||
`getrandom(2)`), but it is the T1 shape that produced the COLDCARD defect, now with key
|
||||
material in the blast radius. Five layers: sealed allowlist trait at key-gen seams;
|
||||
`clippy.toml` `disallowed-methods` ban (compile-time, CI-enforced — no `clippy.toml`
|
||||
exists yet); `cargo-deny` on duplicate `rand` majors (absorbs R-05); degenerate-entropy
|
||||
runtime check; persist the CSPRNG-readiness verdict (absorbs R-09). Also retires the
|
||||
`impl rand::CryptoRng for CountingRng` false promise at `seed.rs:656`.
|
||||
**Gated: do not start until the concurrent Phase 1 agent is done and synced.**
|
||||
|
||||
## Tier 1 — Medium effort, unblocked
|
||||
|
||||
|
||||
@@ -156,7 +156,8 @@ action of the "your seed may be predictable, migrate now" kind is warranted —
|
||||
| F-07 | **Medium** | No `cargo audit`/`cargo deny` in CI; two `rand` majors coexist in the graph | `core/archipelago/Cargo.toml:68` |
|
||||
| F-08 | **Low** | 24-word master mnemonic persisted in browser `sessionStorage` during onboarding | `neode-ui/src/views/OnboardingSeedGenerate.vue:330` |
|
||||
| F-09 | **Low** | Modulo bias in TOTP backup-code generation | `core/archipelago/src/totp.rs:305` |
|
||||
| F-10 | **Low** | Container `generated_secrets` use `thread_rng()` rather than an explicit `OsRng` (same T1 shape as F-02, smaller blast radius) | `core/archipelago/src/container/secrets.rs:92`, `:101` |
|
||||
| F-10 | **Low** → **see F-10a** | Container `generated_secrets` use `thread_rng()` rather than an explicit `OsRng` (same T1 shape as F-02, smaller blast radius) | `core/archipelago/src/container/secrets.rs:92`, `:101` |
|
||||
| F-10a | **Medium** | **Scope correction to F-10 (2026-08-02):** the defaulted-RNG surface is crate-wide — **41 call sites across 15 files**, not 2 — and includes ecash and X3DH key material | `core/archipelago/src/session.rs` (16), `wallet/bdhke.rs` (4), `mesh/x3dh.rs` (2), `storage_crypto.rs`, +11 more — full table in §F-10a |
|
||||
| F-11 | **Informational** | `Math.random()` inside a seed-handling view (benign — UX challenge selection only) | `neode-ui/src/views/OnboardingSeedVerify.vue:159` |
|
||||
| F-12 | **Informational** | Identical default OS credentials on every flashed node | `image-recipe/archipelago-scripts/install-to-disk.sh:205` |
|
||||
| F-13 | **High** | BIP-84 account **private** key is imported into Bitcoin Core's wallet, duplicating the spending key outside the encrypted envelope | `core/archipelago/src/api/rpc/bitcoin.rs:203`, `:229-231` |
|
||||
@@ -488,6 +489,62 @@ One-line change each; batched into the same backlog item.
|
||||
|
||||
---
|
||||
|
||||
### F-10a — Scope correction: the defaulted-RNG surface is crate-wide — **Medium**
|
||||
|
||||
> **Added 2026-08-02, after the original audit.** F-10 above reported this defect as two call
|
||||
> sites in one file. That was **understated**. This section records the true scope with evidence.
|
||||
> F-10's own text and remediation are left unedited above so the correction is auditable rather
|
||||
> than retroactive.
|
||||
|
||||
**Evidence.** `grep -rn "rand::random\|thread_rng()" core/archipelago/src --include=*.rs` returns
|
||||
**43 matches across 16 files**. Two of those (`seed.rs:87`, `:671`) are comments in the
|
||||
already-remediated F-02 file, leaving **41 matches across 15 files**:
|
||||
|
||||
| File | Matches | Generates |
|
||||
|---|---|---|
|
||||
| `core/archipelago/src/session.rs` | 16 | session tokens |
|
||||
| `core/archipelago/src/api/rpc/package/pine_ha.rs` | 6 | app credentials |
|
||||
| `core/archipelago/src/wallet/bdhke.rs` | 4 | **Cashu blinded-key-exchange values — key material** |
|
||||
| `core/archipelago/src/mesh/x3dh.rs` | 2 | **X3DH key agreement — key material** |
|
||||
| `core/archipelago/src/container/secrets.rs` | 2 | `generated_secrets` (the original F-10) |
|
||||
| `core/archipelago/src/api/rpc/package/install.rs` | 2 | install-time secrets |
|
||||
| `core/archipelago/src/storage_crypto.rs` | 1 | **ChaCha20-Poly1305 nonce — reuse breaks the AEAD** |
|
||||
| `core/archipelago/src/credentials/store.rs` | 1 | credential store material |
|
||||
| `core/archipelago/src/device_tokens.rs` | 1 | device tokens |
|
||||
| `core/archipelago/src/federation/invites.rs` | 1 | federation invites |
|
||||
| `core/archipelago/src/bitcoin_rpc.rs` | 1 | Bitcoin RPC password |
|
||||
| `core/archipelago/src/totp.rs` | 1 | TOTP backup codes (also F-09) |
|
||||
| `core/archipelago/src/transport/chunking.rs` | 1 | chunk identifiers |
|
||||
| `core/archipelago/src/fips/dial.rs` | 1 | dial jitter/identifiers |
|
||||
| `core/archipelago/src/api/rpc/auth.rs` | 1 | auth-path material |
|
||||
|
||||
**Per-site production-vs-test classification is deliberately NOT asserted here** — it is the
|
||||
first task of the remediation, not an assumption of this correction. The counts above are raw
|
||||
matches.
|
||||
|
||||
**Assessment.** Unchanged from F-10 in kind: `rand::random()` and `thread_rng()` are backed by
|
||||
ChaCha12 seeded from `getrandom(2)` on `rand 0.8.5`, so **nothing in this table is broken
|
||||
today**. What changes is the *blast radius* of the T1 structural objection. F-10 rated this Low
|
||||
on the basis of "per-app credentials rather than the master key hierarchy". That justification
|
||||
does not survive the true scope: `wallet/bdhke.rs` and `mesh/x3dh.rs` generate key material, and
|
||||
`storage_crypto.rs:39` draws an AEAD nonce, where a silent rebinding to a non-cryptographic PRNG
|
||||
would be catastrophic rather than merely undesirable. Re-rated **Medium**.
|
||||
|
||||
**Why the original audit missed it.** F-10 was reached by tracing the *manifest secrets* path
|
||||
(secret class 4). No step enumerated defaulted-RNG use across the whole crate independently of
|
||||
the traced paths — so files outside those traces were never in scope to be looked at. Recorded
|
||||
here because the same blind spot would recur in the next audit run under the same method.
|
||||
|
||||
**Remediation → tracked as KEY-05 in Phase 10** (`.planning/ROADMAP.md`), which supersedes R-13:
|
||||
a sealed allowlist trait so only approved RNGs can be passed at key-generation seams; a
|
||||
`clippy.toml` `disallowed-methods` ban on `rand::thread_rng` / `rand::random` crate-wide so the
|
||||
default cannot be inherited by *new* code either; `cargo-deny` failing on duplicate `rand`
|
||||
majors (R-05, the mechanism by which a bump could silently rebind); a degenerate-entropy runtime
|
||||
check before key generation; and persisting the CSPRNG-readiness verdict (R-09) that
|
||||
`seed.rs:59` already computes but discards.
|
||||
|
||||
---
|
||||
|
||||
### F-11 — `Math.random()` inside a seed-handling view — **Informational (benign)**
|
||||
|
||||
**Evidence.** `neode-ui/src/views/OnboardingSeedVerify.vue:157-163`, `pickRandomIndices` uses
|
||||
@@ -899,7 +956,8 @@ own `/gsd-plan-phase`, not an opportunistic edit.
|
||||
| **R-10** | [ARCHY-2] hardening | Add a test asserting the `getrandom` crate uses the **blocking** syscall, so the invariant is mechanical rather than a comment | `core/archipelago/src/seed.rs` | S | no |
|
||||
| **R-11** | F-08 (Low) | Clear `_seed_words` on route-leave from onboarding, not only on successful verify; add a wall-clock expiry mirroring `MNEMONIC_TTL` | `neode-ui/src/views/OnboardingSeedGenerate.vue`, `OnboardingSeedVerify.vue` | S | no |
|
||||
| **R-12** | F-09 (Low) | Replace `% charset.len()` with `SliceRandom::choose(&mut OsRng)` and pin the uniformity property with a test | `core/archipelago/src/totp.rs` | S | no |
|
||||
| **R-13** | F-10 (Low) | Swap `random_hex` / `random_base64` from `thread_rng()` to explicit `OsRng` | `core/archipelago/src/container/secrets.rs` | S | no — **blocked**: file has another agent's uncommitted work |
|
||||
| **R-13** | F-10 (Low) | ~~Swap `random_hex` / `random_base64` from `thread_rng()` to explicit `OsRng`~~ — **SUPERSEDED 2026-08-02 by R-16**; this file is 2 of 41 sites | `core/archipelago/src/container/secrets.rs` | S | no |
|
||||
| **R-16** | **F-10a (Medium)** | Crate-wide enforcement so a defaulted RNG cannot be inherited anywhere: sealed allowlist trait at key-generation seams; `clippy.toml` `disallowed-methods` ban on `rand::thread_rng`/`rand::random` (compile-time, CI-enforced); `cargo-deny` on duplicate `rand` majors; degenerate-entropy runtime check; persist the CSPRNG-readiness verdict (absorbs R-05, R-09, R-13) | 15 files — see §F-10a | **PHASE** — tracked as **KEY-05**, Phase 10 | no |
|
||||
| **R-14** | F-11 (Informational) | One-line comment at `pickRandomIndices` recording that the `Math.random()` is a UX challenge selector, not key material | `neode-ui/src/views/OnboardingSeedVerify.vue` | S | no |
|
||||
| **R-15** | §6 checklist | Run the on-node verification checklist — especially C-3 (per-node SSH/TLS keys) and C-5 (cross-node collision test) | — | M | **yes** (2+ nodes from one ISO) |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user