f394c020558b802053a50ff27fa17c8f174f910a
19
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e205f2c34a |
docs(security): prove the delivery path on hardware; close window 15
Controlled test on archi-dev-box with operator approval. The daemon was
stopped first so the reconciler could not repair the state before the
re-exposure was confirmed — without a confirmed 200, the later 401 would
be consistent with the state never having been broken at all.
1. stale conf installed + container restarted -> POST /bitcoin-rpc/
returned 200 with a real block height and Allow-Origin: *
2. daemon started 20:00:36, nothing else touched
3. 20:02:19 reconcile rendered the conf and logged the expected warn
line naming bitcoin-ui/archy-bitcoin-ui, then restarted it
4. POST -> 401, Allow-Origin origin-scoped
5. conf byte-identical to the pre-test known-good, container healthy
Both halves are now proven on real hardware: a05956c4's template (the
gate works) and f6b5245b's delivery path (the gate reaches a container
the reconciler had been skipping).
Also records the operator's decision AGAINST credential rotation — no
macaroon, no Bitcoin RPC password — with the trade it accepts stated
plainly, so it is not silently re-litigated later.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
5a21b58f69 |
docs(security): record what actually closed :8334, and what it does not prove
The node is closed and verified 401 with origin-scoped CORS. But an
unrelated bitcoin-ui rebuild at 18:36 cleared the stale conf before the
reconcile fix was deployed at 19:06, so the 401 proves a05956c4's
template and NOT the delivery path
|
||
|
|
ba493fb0fc |
docs(security): write up the Bitcoin RPC proxy that stayed open after it was fixed
The half that landed correctly (LND, clean 401) made the half that did not harder to notice, because the first check an operator would run returns a pass. Records the probes, the three-fact root cause, and the pass condition for re-probing a node. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a3283cffb4 |
feat(10-06): enable the entropy lint and supply-chain gates (KEY-05 b/c)
Layer (b) — core/clippy.toml bans rand::random and rand::thread_rng crate-wide, each with a reason naming KEY-05 and pointing at the evidence doc. No CI change was needed: the Rust job already runs `cargo clippy --all-targets --all-features -- -D warnings` from core/, so a disallowed_methods hit is already a build failure. --all-targets covers tests deliberately — a fixture keeping the default is a template for the next production call site. Ordering was asserted before the file was written, not after: the residual count of unmigrated call sites is 0, so this cannot turn CI red for other agents on this shared tree. Layer (c) — core/deny.toml makes the rand major split change-detecting: global multiple-versions = "allow", a per-crate deny-multiple-versions for rand, and a dated grandfather skip pinning =0.9.2 exactly. The tree as it stands passes; a third version or a change to either member fails. Both gates were OBSERVED working, not assumed: - Reintroducing one banned call produced the disallowed_methods error with the reason text reaching the developer at the failure point; reverting returned the residual count to 0. - `cargo deny check bans` exits 0 as-is. Removing the grandfather entry made it exit 2 and print both dependency trees, independently confirming F-07's account of where each rand version comes from. Restored, it exits 0 again. Policy (checkpoint Task 5, human-approved): bans-only. The advisories gate is NOT enabled — it fails builds when a new CVE is published against an existing dep with no local change, which on a tree where several agents push continuously would block everyone at an arbitrary hour, with remediation often meaning a bump to an exactly-pinned crypto dependency. No break-glass procedure exists. F-07's advisory half stays OPEN and is recorded as such. cargo-deny is pinned to 0.20.2 and installed from crates.io rather than via EmbarkStudios/cargo-deny-action, because that action exposes no input to pin the tool version — an unpinned supply-chain checker would reintroduce, at the CI layer, the exact "backend fixed by configuration rather than stated" shape this plan exists to remove. crates.io is also the source vetted at the Task 5 legitimacy gate (EmbarkStudios, repo resolves, ~4.79M downloads). RECORDED HONESTLY: layer (b)'s gate is live but not yet EFFECTIVE. The tree carries 42 pre-existing clippy warnings — unused imports, dead code, ~39 style lints — that are already errors under -D warnings, so that CI step cannot pass today for reasons unrelated to KEY-05. Until a dedicated lint-clearing pass lands, a new banned RNG call would be one error among many rather than a distinctive build-stopper. Pre-existing and out of scope; clearing it right before an OTA would be poor sequencing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a806a658a6 |
docs(10-04): C-3 FAILED — three live nodes share their SSH host keys
Audit checklist item C-3 ("the highest-value check here") is no longer
UNVERIFIED. It failed, and the failure is a live F-03 instance rather than a
theoretical one.
Three distinct fleet nodes — archipelago-1, archy-x250-beta and archipelago —
present byte-identical ECDSA, ED25519 and RSA host key fingerprints. Two of
them (archipelago-1, archy-x250-beta) also present the same TLS certificate,
so they share the TLS private key as well.
Gathered read-only and remotely: ssh-keyscan plus an anonymous TLS handshake.
No node was logged into, nothing was written, nothing was rotated. A weaker
instrument than the checklist's on-node commands, chosen because it needs no
access and therefore covers the reachable fleet rather than two nodes — and it
is sufficient for the FAIL condition, which is any fingerprint appearing twice.
Ruled out the obvious alternative (one machine registered three times on the
tailnet): all three answered live TCP within the same minute, and tailscale
ping resolves them to different physical endpoints on different continents
under different tailnet accounts.
One finding worth more than the count: `archipelago` has a UNIQUE TLS cert
(CN=austin-sapien) and SHARED SSH host keys, because it was renamed and
server.set-name re-mints the cert via regenerate_tls_cert() while touching
nothing else. So TLS uniqueness is not evidence that a node's key material is
per-node — any renamed node gets a unique certificate for free. Checked on TLS
alone, that node would have looked clean. Recorded because it justifies the
audit script reporting the two key classes separately instead of issuing one
node-level verdict.
All three are listed under "shared verdict, deliberately not rotated" with the
reason and the next step. A verification task that remediates is a
verification task that takes a node offline.
Also records what this does NOT establish, each with the evidence still
needed: same-ISO provenance, the script's own verdict on those nodes, that a
rotation preserves the operator's live session on real hardware, that
host_secrets reaches system.stats on a real node, and the four nodes that were
unreachable at scan time.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
f2f89b5fe3 |
docs(10-02): record C-6 evidence so far — probe-method correction, 3 transports still open
C-6 is NOT closed by this commit and is not marked verified.
Measured (read-only, on-node):
- loopback and self-LAN-IP: auth.isOnboardingComplete 200 (EXPOSED),
seed.status 401 (session enforcement intact) — no stop-the-plan finding.
- /rpc/ returns 404: nginx's second proxy block is not a second door, so
the unauthenticated surface is reachable through /rpc/v1 only.
NOT measured — needs a second machine: LAN, Tor, FIPS mesh ULA.
NOT performed — the KEY-01 refusal check and the fresh-node onboarding
walkthrough. No node runs 10-01's gate yet: the installed binary was built
at 06:37 and
|
||
|
|
257ca7e6ac |
docs(10-06): classify all 43 defaulted-RNG call sites with file:line evidence (KEY-05, F-10a)
F-10a recorded raw grep counts and deliberately declined to classify them. This resolves that: every one of the 43 matches under core/archipelago/src now carries a production/test verdict (evidenced by its file's `#[cfg(test)] mod tests` line), what the drawn value becomes, whether the degenerate-entropy guard applies, and a disposition. Tally: 41 migrate, 2 comment, 0 allow. No site needed an exemption, so the crate-wide ban will have no holes to audit. Two corrections to F-10a, each derived independently with its evidence line: session.rs is 4 production sites not 16 (mod tests begins :471), and mesh/x3dh.rs:100/:114 are u32 prekey identifiers, not key material -- the X25519 secrets come from crypto::generate_x25519_ephemeral() at :99/:113. The enforcement blast radius is pinned with `cargo metadata` output rather than asserted: models, helpers and js-engine are not workspace members, so the two core/models matches are outside the clippy build graph and are recorded as a stated limitation rather than omitted. Requirement: KEY-05. Supersedes R-13, absorbs R-05 and R-09. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
96dba73a16 |
docs(10-04): record D-06 rotation trigger as detect-report-then-apply
Task 1 of 10-04 is a blocking decision checkpoint, rated one-way: rotating a node's SSH host key invalidates every known_hosts entry for it fleet-wide and the old private key is destroyed by the swap. Chosen: detect-report-then-apply. auto-on-boot would fire simultaneous known_hosts breakage across the fleet during an OTA with no operator holding the new fingerprints, and a rotation that fails partway on a remote node (.228 is at a remote site and in real use) needs physical console access. It also cannot be dev-paired, which contradicts the standing verify-on-the-dev-pair- first policy — by the time it has been observed on the dev pair it has already run everywhere. The cost of the chosen option — exposure persists on any node nobody revisits — is bounded by making the verdict visible in system.stats rather than by automation, and by keeping a list in this document of every node that reported `shared` and was deliberately not rotated. Records what the decision binds: detect-only default, --apply inert without --yes, the boot unit carries no apply path, and --apply --yes refuses on any node whose verdict is not `shared`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0d513a0ef7 |
docs(10-05): record the Core-wallet fleet census — 4 nodes clear, 6 unchecked (D-07b)
Task 3 of plan 10-05, run by the operator over Tailscale on 2026-08-02 using the read-only procedure in KEY-03-SIGNING-POSTURE.md. No escalation: nothing found. Examined and CLEAR (4): archi-dev-box, shorty-s/.228, archy-x250-beta, archy-x250-pa. On every one there is no wallet named `archipelago` — the deleted handler's default wallet_name — `listwallets` returns only the unnamed default, and that default reports blank=true, keypoolsize=0, txcount=0, balance=0. The only named wallets are Fedimint gatewayd-*. The result holds across two container vintages (bitcoin-knots and bitcoin-core), so it is not four copies of one image behaving identically. Not examined (6), recorded with reasons rather than omitted: framework-pt, archipelago-1, archipelago and archy-dev-pa (SSH permission denied — password rotated/not held), archipelago-5 (timed out during banner exchange), and archy-x250-dev (offline). Password auth was deliberately not attempted: several fleet nodes lock PAM quickly on a wrong password, and locking out an in-use production node is a worse outcome than an incomplete census. The conclusion is stated at the strength the evidence supports — no *examined* node holds a wallet the deleted handler created, and no examined node holds any wallet with keys or funds. It is deliberately NOT generalised to "the fleet is clear" while six nodes are unknown. F-13 is closed by deletion regardless: the code that could create such a wallet is gone from every future build. No key material appeared in any output and `listdescriptors true` was never run. Also corrects the now-stale R-04/F-13 entry in UNIFIED-TASK-TRACKER.md, which still described `handle_bitcoin_init_wallet_from_seed` and a watch-only migration as pending work — that code no longer exists. Marks it done-by- deletion and adds the six unchecked nodes as a standing item, flagged as a natural fold-in for KEY-04's on-node work but tracked independently so it does not vanish if KEY-04 is re-scoped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
262998747e |
feat(10-05): report BIP-32 key origin on lnd.create-psbt, and record the honest signing posture (D-07b/D-09)
With Bitcoin Core's wallet deleted, LND's PSBT round trip is the only external-signer path Archipelago has, and D-09's key-origin protection moves from Core descriptors (of which none remain) to the PSBT itself. Adds `psbt_key_origin_report(&str) -> Result<PsbtKeyOriginReport>` to lnd/wallet.rs, reporting `input_count`, `inputs_with_key_origin` and `all_inputs_have_key_origin`. An input counts as carrying key origin when either its `bip32_derivation` or `tap_key_origins` map is non-empty. A PSBT with zero inputs reports false rather than vacuous truth. Parsed with the already-present `bitcoin` and `base64` crates; no dependency added. `lnd.create-psbt` gains an additive `key_origin` object on its response and a `tracing::warn!` with the counts when key origin is missing, because that is the exact condition under which a hardware signer refuses the PSBT. Computed best-effort: a decode failure degrades to `null`, never to an error, so a user's send cannot fail because an inspection helper could not parse something. `handle_lnd_finalize_psbt` and `handle_lnd_create_raw_tx` (which deliberately auto-signs with LND's hot keys) are untouched. Three tests, with fixtures built programmatically from the `bitcoin` crate rather than pasted as opaque base64: with-derivations, without-derivations, and malformed-is-an-error-not-a-panic. KEY-03-SIGNING-POSTURE.md gains an honest per-step coverage map of the fund -> export -> sign offline -> import -> finalize -> broadcast round trip. Of six steps, only the new inspection has automated coverage; steps 1, 4, 5 and 6 have none, and there is no air-gap transport (no animated QR, no .psbt file exchange) — export/import is copy-paste of base64. Untested paths are named as untested. Records the verdict that decides whether any of this is an air gap: on a default node an external signer CANNOT meaningfully sign a PSBT from `lnd.create-psbt`, because LND holds the keys for every input it selects. Evidence: the PSBT is funded from LND's own wallet; `ensure_wallet_initialized` creates a full key-holding wallet via /v1/initwallet; the generated lnd.conf carries no `remotesigner.*` block; and a search of apps/, scripts/, core/archipelago/src and image-recipe/ for remotesigner/createwatchonly/ nochainbackend returns zero matches. No fleet node is provisioned watch-only. What ships is PSBT transport, not air-gapped custody — the gap is provisioning, not plumbing. Adds the standing honesty statement in its own subsection: Lightning channel, revocation and HTLC keys are NOT air-gappable at all. They must sign in real time to answer counterparty commitments; remote signing relocates them to a hardened host, it does not cool them. Also adds a status banner to PSBT-SIGNING-ARCHITECTURE.md recording that its Phase 1 was superseded by deletion rather than delivered, so §0's "single highest-value change" and §2.1's invariant now read against a code path that no longer exists. Banner only; §5.4's honesty table is byte-identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
9622926868 |
fix(10-05): delete the Bitcoin Core wallet path that duplicated the spending key (F-13, D-07b)
`handle_bitcoin_init_wallet_from_seed` derived the BIP-84 account extended *private* key, stringified it, and imported `wpkh(xprv/0/*)` / `wpkh(xprv/1/*)` into a Bitcoin Core descriptor wallet created with `disable_private_keys=false` and an empty passphrase. That put a second copy of the node's spending key in Core's `wallet.dat`, outside the daemon's Argon2 + ChaCha20-Poly1305 envelope. That duplication into weaker protection was audit finding F-13 (High). Deleted rather than rewritten watch-only (D-07b supersedes D-07/D-07a): - No caller anywhere. Repo-wide search leaves exactly one occurrence of the method name (its own dispatcher registration) and two of the symbol in code (definition + dispatch call); every other hit is prose in docs. - LND is the wallet the product drives. Across neode-ui/src every `bitcoin.*` call is read-only status (getinfo/prune-status/onion); the wallet UI sends via `lnd.sendcoins`. - It never ran on archi-dev-box: no wallet named `archipelago` exists there, and the one loaded wallet reports blank=true, keypoolsize=0, txcount=0. - It was authenticated AND password-gated, so F-13 was key-at-rest duplication, not an exposed endpoint. No migration is performed and none is planned. This removes code, not wallets: nothing on disk is touched, no funds move, no wallet.dat is modified. If a node is ever found holding a wallet this handler created, that is a finding to surface and stop on, not a trigger to auto-migrate. `seed::derive_bitcoin_xprv` loses its only non-test caller and is retained deliberately with `#[allow(dead_code)]` and a stated reason: it keeps its existing test coverage and it is the derivation D-07c's deferred BDK cold vault will need. Records the evidence, the D-08/D-09 consequences and the D-07c deferral in docs/security/KEY-03-SIGNING-POSTURE.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2efab5f219 |
fix(10-03): unify secret generation to a single producer + self-heal (F-03)
Unify rather than delete. The defect in F-03 was never "a second attempt to
create a key exists" — it was that failure was silent and the completion marker
lied about it. A second attempt is only dangerous when it is an unaudited second
PRODUCER carrying its own idea of success, its own absent retry policy and its
own absent failure record.
Single producer. gen_tls() is now the only code in the ISO build that creates
/etc/archipelago/ssl/archipelago.{key,crt}; gen_ssh() the only code that creates
/etc/ssh/ssh_host_*. Two secondary producers are gone:
- the Dockerfile's `openssl req` layer, which baked a keypair the strip layer
deleted moments later in the same build;
- the installer's "ensure SSL cert exists for nginx HTTPS" block, which before
the strip almost never fired and after it would have fired on every install.
Proof is mechanical, not a claim: every executable `openssl req` / `ssh-keygen
-A` invocation in the builder now lives inside the generator heredoc, and the
test suite fails if one appears outside it.
Build-time assertion. The one realistic total failure is a missing generator
binary, which is deterministic — no retry or reboot fixes it. A rootfs RUN layer
now fails the build if openssl or ssh-keygen is missing or non-executable.
openssl and openssh-server are both already in the package list (and
openssh-server hard-depends openssh-client, which ships ssh-keygen), so today
this is cheap insurance; it earns its place the first time someone edits that
list.
Self-heal, never dead-end. Fail-closed governs SERVING; retry governs
RECOVERING, and they are different things. Adds
archipelago-first-boot-secrets.timer (OnBootSec=5min, OnUnitActiveSec=15min),
installed and enabled with a hand-written symlink fallback because chroot
systemctl enable can fail silently. The service's own ConditionPathExists=!
makes every trigger a no-op once the marker exists, so a healthy node pays
nothing. On success the script now restarts consumers that are in `failed` —
try-reload-or-restart is a no-op on a failed unit, so without this a recovered
node would have valid keys on disk and nginx still down.
Never serve a bogus key. gen_tls parses both halves back with `openssl pkey`
and `openssl x509` before the swap, so a truncated or half-written artefact is
never what nginx reads.
Tests: 6 cases, each with an isolated negative control (transcripts in SUMMARY).
- case 4, TLS fails every attempt on a stripped root -> no key from any source.
Control: reintroduce a fallback key creation -> only case 4 red.
- case 5, self-heal: a failed run then a later successful run -> key present,
marker set, failed units restarted. Control: dead-end on a node that already
failed -> only case 5 red.
- case 6, single-producer invariant. Control: reintroduce the installer block
-> only case 6 red, naming the line.
Residual risk, stated plainly: a machine where generation can never succeed
still ends up with no SSH and no TLS. Build-time assertion removes the
deterministic cause, retry plus timer removes the transient ones, so what
remains is genuinely broken hardware — and it says so on the console and in
/var/lib/archipelago/first-boot-secrets.failed rather than quietly serving a
key nobody audited.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
ff6902dd9d |
docs(10): add independent verification guide for auditors
A guide a third-party security auditor can use to verify Phase 10's claims without trusting our test harness — and that we use ourselves. Every claim carries four parts, all required: the claim stated falsifiably; how to REPRODUCE THE DEFECT on the parent commit; how to verify the fix; and a negative control that must go red on exactly that defect and nothing else. A test passing on both fixed and unfixed code proves nothing, and reproduce-first is the step most often omitted in security theatre. Prefers external checks (curl from another host, tar listing, cross-node file comparison) over our own tests wherever a claim can be checked from outside. Tiered by hardware needed: Tier 0 any checkout, Tier 1 running node, Tier 2 ISO build host, Tier 3 two physical nodes, Tier 4 pre-release gate. Status marked per claim — verifiable now, pending a plan, or hardware-gated — so an unmarked absence is never read as a pass. States what is explicitly NOT claimed (Lightning custody is not air-gappable; no claim against a compromised kernel CSPRNG or supply chain; KEY-05 is structural not exploitable), the known-accepted risks with where each was decided, and carries the C-6 warning that probing with seed.status reports the surface closed while the real door stands open. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
201ef474e7 |
docs(10-03): record C-4 build-host evidence procedure (UNVERIFIED)
Task 3 of 10-03 is a blocking checkpoint: proving the shipped rootfs tar is identity-free needs a real ISO build host with podman/docker and disk for a full rootfs rebuild. This commits the prepared evidence document with the exact command sequence, marked UNVERIFIED, rather than claiming the check passed. The document states the inverted expectation explicitly. The audit's C-4 entry expected SSH host keys and the TLS key to be PRESENT — that described the broken state it was measuring. After the strip layer those must be ABSENT, so the audit's stated expectation is now the failure condition. A future reader comparing the two would otherwise conclude the check regressed. Also records two things the operator would otherwise get wrong: - RECIPE_HASH must be read from the stamp file, not computed from the repo file. build-debian-iso.sh rewrites the builder's relative paths into a temp copy before exec, and the hash covers "$0"; the hashed region has 35 such rewritten expressions plus an absolutised SCRIPT_DIR, so the value is specific to the build host and checkout path. - C-4 is a build-host check only. Two-node key divergence is C-3 and stays separately UNVERIFIED; the note explains why SSH host keys are the sharper signal there than TLS, given the installer's per-install TLS fallback. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
90ce4bcc46 |
docs(10): correct F-10a's own overstatement — x3dh sites are identifiers, not key material
The F-10a scope correction committed hours earlier asserted semantics its evidence did not support. The KEY-05 planner caught it against the code: - mesh/x3dh.rs:100/:114 are u32 prekey IDENTIFIERS (spk_id, otk_id), not key agreement material. The X25519 secrets come from crypto::generate_x25519_ephemeral() at :99/:113 and were never in scope. - session.rs's 16 raw matches read as 16 production token sites; #[cfg(test)] begins at :470, so it is 4 production + 12 test. - wallet/bdhke.rs is 2 production of 4 (#[cfg(test)] at :143) — and those two ARE genuine key material: generate_secret() :133 and random_blinding_factor() :139. The Medium rating still holds, on narrower grounds: bdhke's two production sites plus storage_crypto.rs:39's AEAD nonce. It no longer rests on x3dh. Struck rather than silently rewritten. F-10 was corrected on the grounds that understatement misleads the next reader; overstatement does the same, and this table managed both within a day. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5cf44c9a58 |
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> |
||
|
|
5ba80e49b7 |
docs(quick-260731-upz): entropy-audit remediation backlog + tracker items
- Adds F-13 (High) to the audit: the BIP-84 account PRIVATE key is imported into Bitcoin Core (bitcoin.rs:203 disable_private_keys=false, :229-231 wpkh(xprv/...)), so the spending key is persisted outside the Argon2 envelope in a wallet with an empty passphrase; the descriptors also carry no [fingerprint/derivation] key origin, so no hardware signer could ever use them. Found by tracing secret class (1) end-to-end - Fills the Remediation Backlog: R-00..R-15, prioritised severity x effort, each with the finding it closes, files, effort, and hardware gating; plus an explicit "not implemented here, and why" section - Records ARCHY-1 as APPLIED with the exact test evidence and an honest note that making the source explicit removes a future failure mode rather than repairing a past one - Wires the resulting open items into docs/UNIFIED-TASK-TRACKER.md in its existing tier/checkbox format: Tier 0 (cargo audit/deny CI, ceremony mnemonic input, a five-item hygiene batch, secrets.rs OsRng), Tier 1 (ISO fail-open first-boot secrets, Argon2 vs ADR-005, the on-node checklist), Tier 2 (the Critical unauthenticated seed RPCs, PSBT Phase 1, PSBT phases 2-7, seed-RPC transport confinement) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5faf1a3c5f |
docs(quick-260731-upz): PSBT-first signing architecture spec
Watch-only descriptor wallets, external signers, multisig, air-gap transport and honest LND limits — a spec a future /gsd-plan-phase can consume. - Names the current gap: bitcoin.rs:203 passes disable_private_keys=false and bitcoin.rs:229-231 imports wpkh(xprv/...), so Core holds the BIP-84 account PRIVATE key today. Closing that is Phase 1 and unblocks everything else - Full Core RPC loop with wallet- vs node-scoped RPCs; analyzepsbt drives UI - Tier 1 single-sig with mandatory [fingerprint/derivation] key origin; Tier 2 wsh(sortedmulti) on BIP-48; taproot/MuSig2 deferred as UNVERIFIED - BC-UR v2 primary (fountain-coded, degrades gracefully), BBQr for Coldcard, file fallback always; animated multi-frame is mandatory, not optional - LND: channel/revocation/HTLC keys CANNOT be air-gapped; funding tx must NEVER be self-broadcast (type-level refusal, not a boolean) - On-chain (PSBT-protectable) vs lightning (necessarily hot) split, with the exact user-facing sentence the UI must use - Hot wallet kept as explicitly-secondary with server-enforced limits; safe path is the DEFAULT, per T1's survivors - Migration section refuses to over-alarm: the audit found no entropy defect, so no Archipelago user needs to rotate a seed - 7 phases with dependencies, candidate requirements, and hardware gating - Answers two open items in docs/hardware-signer-design.md - Flags bitcoin-knots:latest as an unpinned tag vs ADR-009 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f11db4ea1d |
docs(quick-260731-upz): entropy & seed-generation security audit
Evidence-backed audit of every secret class against the real tree, prompted by the 2026-07-30 Coinkite COLDCARD entropy incident. - No Coldcard-class entropy defect exists: no non-cryptographic PRNG, no clock-seeded key, no Math.random() in any browser key path - F-01 (Critical, NOT entropy): seed.generate/seed.restore are unauthenticated, unrated, and unconditionally overwrite a live node's Ed25519/Nostr/FIPS keys - F-02 [ARCHY-1] CONFIRMED: mnemonic entropy source is a bip39 transitive default, not a call-site argument — the exact structural shape of T1 - F-03 (High) [ARCHY-3]: first-boot TLS/SSH regeneration is fail-open and its completion marker is set even on failure, over a fleet-shared cached rootfs - ARCHY-2 confirmed good; ARCHY-5 refuted as a present defect (32 | 256) - Argon2::default() is 19MiB/t=2, not ADR-005's stated 64MB/3 - Corrects the scoping assumption that image-recipe/_archived/ is dead: it is the live ISO builder, exec'd by build-debian-iso.sh - Adds a "What we do right" section and an UNVERIFIED on-node checklist Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |