Commit Graph
16 Commits
Author SHA1 Message Date
archipelagoandClaude Opus 5 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>
2026-08-02 16:58:42 -04:00
archipelagoandClaude Opus 5 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>
2026-08-02 15:06:32 -04:00
archipelagoandClaude Opus 5 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 879de59e landed at 13:05, and the refusal string is absent
from it. A --destructive run against the dev-box would replace its identity
rather than be refused, so it was not made.

Also carries 10-01's pre-OTA fleet check (onboarding.json complete-true
without user.json): dev-box safe, rest of fleet unchecked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 14:25:05 -04:00
archipelagoandClaude Opus 5 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>
2026-08-02 14:21:40 -04:00
archipelagoandClaude Opus 5 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>
2026-08-02 14:20:53 -04:00
archipelagoandClaude Opus 5 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>
2026-08-02 11:32:50 -04:00
archipelagoandClaude Opus 5 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>
2026-08-02 10:08:42 -04:00
archipelagoandClaude Opus 5 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>
2026-08-02 10:08:14 -04:00
archipelagoandClaude Opus 5 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>
2026-08-02 09:30:06 -04:00
archipelagoandClaude Opus 5 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>
2026-08-02 09:14:27 -04:00
archipelagoandClaude Opus 5 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>
2026-08-02 08:57:08 -04:00
archipelagoandClaude Opus 5 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>
2026-08-02 07:30:43 -04:00
archipelagoandClaude Opus 5 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>
2026-08-02 07:01:00 -04:00
archipelagoandClaude Opus 5 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>
2026-08-01 06:02:31 -04:00
archipelagoandClaude Opus 5 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>
2026-07-31 23:09:17 -04:00
archipelagoandClaude Opus 5 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>
2026-07-31 23:03:26 -04:00