tasks: decompose review #004 findings into 4 fix tasks + review gate

W1 (call/protocol/abort-cascade-wiring): wire AbortCascade into CallAdapter
handle_stream for EVENT_ABORTED. W2 (core/endpoint-client-fingerprint):
extract TLS client cert fingerprint in dispatch_quinn/dispatch_iroh.
W3 (vault/mnemonic-debug-redaction): replace Mnemonic derive(Debug) with
redacting impl. W4 (core/auth-apikey-resources, level: research): decide
whether ApiKeyEntry should carry resources, then implement or drop from
spec. review-post-impl-fixes gates on all four. Graph: 33 tasks, 12 gens.
This commit is contained in:
2026-06-24 10:02:03 +00:00
parent d904dfc243
commit d149932e2a
5 changed files with 571 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
---
id: vault/mnemonic-debug-redaction
name: Replace Mnemonic derive(Debug) with redacting impl to prevent seed phrase leak
status: pending
depends_on: []
scope: single
risk: low
impact: isolated
level: implementation
---
## Description
`Mnemonic` (crates/alknet-vault/src/mnemonic.rs:35) currently derives
`Debug`:
```rust
#[derive(Debug)]
pub struct Mnemonic {
inner: Bip39Mnemonic,
phrase: String,
}
```
The derived `Debug` prints both fields, including `phrase: "abandon
abandon abandon ..."`. The crate's own module doc (mnemonic.rs:8) says
"Seed material is protected with `Zeroize`" and the `Mnemonic::phrase()`
accessor (line 82) warns "Handle with care — this is the root of trust
for all derived keys." The `Zeroize + Drop` impls (lines 8899) wipe
the phrase from memory on drop — but `Debug` will happily hand the
phrase to any `tracing::debug!`, `format!("{:?}")`, panic backtrace,
or error-context printer that touches a `Mnemonic` value.
This is inconsistent with the rest of the crate's secret handling:
- `DerivedKey` has a custom redacting `Debug` (protocol.rs:4856)
printing `private_key: "[REDACTED]"`.
- `EncryptionKey` has a custom redacting `Debug` (encryption.rs:132139).
- `Capabilities` has a redacting `Debug` (types.rs:112118).
- `Secret<T>` has a redacting `Debug` (types.rs:5155).
- `Mnemonic` ... derives `Debug` and prints the phrase.
The root of trust should never have a `Debug` impl that can print it.
A future debugging session that adds `tracing::debug!(?mnemonic, ...)`
would land the seed phrase in a log file.
### Resolution
Replace `#[derive(Debug)]` with a manual impl that redacts, matching
the pattern used for `DerivedKey`:
```rust
impl std::fmt::Debug for Mnemonic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Mnemonic")
.field("phrase", &"[REDACTED]")
.finish()
}
}
```
Also check `Seed` (mnemonic.rs:107129) — it derives
`#[derive(Clone, Zeroize)]` with `#[zeroize(drop)]` but does not derive
`Debug`. Verify it has no `Debug` impl that prints `bytes`; if it does
(or if a future `derive(Debug)` would), add a redacting impl there too.
### Test
Add a test asserting `format!("{:?}", mnemonic)` does not contain any
word from the generated phrase. Pattern after
`test_derived_key_debug_redacts_private_key` (protocol.rs:106118):
```rust
#[test]
fn test_mnemonic_debug_redacts_phrase() {
let mnemonic = Mnemonic::generate(24).unwrap();
let debug_output = format!("{:?}", mnemonic);
assert!(
debug_output.contains("[REDACTED]"),
"Debug must show [REDACTED] for phrase, got: {debug_output}"
);
for word in mnemonic.phrase().split_whitespace() {
assert!(
!debug_output.contains(word),
"Debug must not leak phrase word '{word}', got: {debug_output}"
);
}
}
```
## Acceptance Criteria
- [ ] `Mnemonic` no longer derives `Debug`; has a manual redacting `Debug` impl
- [ ] `format!("{:?}", mnemonic)` contains `"[REDACTED]"` and no phrase word
- [ ] `Seed` checked — either has no `Debug` impl, or has a redacting one
- [ ] Unit test: `test_mnemonic_debug_redacts_phrase` passes
- [ ] Existing tests still pass (`cargo test -p alknet-vault`)
- [ ] `cargo clippy -p alknet-vault --all-targets` succeeds with no warnings
## References
- docs/reviews/004-post-implementation-sanity-check.md — W3 (full finding)
- crates/alknet-vault/src/protocol.rs:4856 — `DerivedKey` redacting `Debug` (pattern to follow)
- crates/alknet-vault/src/encryption.rs:132139 — `EncryptionKey` redacting `Debug`
- crates/alknet-core/src/types.rs:5155 — `Secret<T>` redacting `Debug`
## Notes
> Small fix, but eliminates a latent root-of-trust leak. The same
> pattern (custom redacting `Debug`) is already established in three
> other places in this codebase — this task brings `Mnemonic` in line.