Files
alknet/tasks/vault/mnemonic-debug-redaction.md
glm-5.2 d149932e2a 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.
2026-06-24 10:02:03 +00:00

3.9 KiB
Raw Blame History

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
vault/mnemonic-debug-redaction Replace Mnemonic derive(Debug) with redacting impl to prevent seed phrase leak pending
single low isolated implementation

Description

Mnemonic (crates/alknet-vault/src/mnemonic.rs:35) currently derives Debug:

#[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:

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):

#[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.