# BCX v0.2.0 Pentest Report

Tag: v0.2.0
Commit: 108ae62abe7ca27692f973c008c161934df44a81
Status: PASS
Tester: Senior Rust Security Architect (Principal Security Engineer)
Date: 2026-06-19
Scope: BCX v0.2.0 post-remediation verification of hybrid verification, release tooling, wire bounds, debug redaction, protocol version invariants, nonce handling, and replay-cache hashing documentation.

## Summary

This report records the release pentest result for BCX `v0.2.0`. The root
`PENTEST.md` scratch input was reviewed, remediated where release-scope fixes
were required, and then captured here by digest before deletion.

Input-Digest: sha256:e51a39f2befbde8ba177e1967972f35a1ac39e6995f41b88b617458d1c23dff3

## Scratch Report

```text
Verification Report — Post-Remediation

---
FINDING 1 (HIGH) — Hybrid Signature Enforcement → FIXED

The new HybridVerifier supertrait with a sealed HybridVerified(()) return type is correctly implemented. Three things lock this down together:

1. Verifier: HybridVerifier forces every Verifier to implement both component methods.
2. verify_detached_bytes dispatches HybridEd25519MlDsa65 envelopes exclusively through verifier.verify_hybrid(...) — the non-hybrid verify() path is unreachable for hybrid envelopes.
3. HybridVerified(()) has a private tuple field, so it can only be constructed inside envelope.rs. An external implementor cannot override verify_hybrid with a shortcut that returns Ok(HybridVerified(())) — the compiler will reject it. The only way to produce a HybridVerified externally is to let the default implementation run, which calls both verify_ed25519 and verify_ml_dsa_65 in sequence.

The new test hybrid_verification_requires_both_components (lines 193–250) correctly demonstrates that a verifier accepting Ed25519 but rejecting ML-DSA-65 propagates InvalidSignature. The sealing is solid.

---
FINDING 2 (HIGH) — CI Silent Skip → FIXED

scripts/checks.sh lines 54–55 are now unconditional:

cargo deny check
cargo audit

No more if ... then ... else printf 'skipping' bypass. Both tools will cause CI to fail immediately if absent from the environment.

---
FINDING 3 (MEDIUM) — u32 as usize Truncation → FIXED

bcx-wire/src/lib.rs:157–161 now uses checked conversion:

let payload_len =
    usize::try_from(self.payload_len).map_err(|_| ValidationError::TooLarge)?;
if payload_len > limits.maximum_message_len() {
    return Err(ValidationError::TooLarge);
}

Correct on all pointer widths. Note: WireHeader::new and validate are now fn instead of const fn, which is required since usize::try_from is not const-stable.

---
FINDING 4 (MEDIUM) — SignatureEnvelope Debug Exposure → FIXED

#[derive(Debug)] has been removed from SignatureEnvelope. The manual Debug impl (lines 176–188) emits [64 bytes] or [3293 bytes] instead of raw byte sequences. The new test signature_envelope_debug_redacts_signature_bytes (lines 171–190) pins the exact output format.

---
FINDING 5 (MEDIUM) — ProtocolVersion Public Fields → FIXED

Fields are now private:

pub struct ProtocolVersion {
    major: u16,
    minor: u16,
}

Accessors major() and minor() are exposed. WireHeader::validate correctly uses these accessors. The ProtocolVersion::new(1, 0) test in src/lib.rs still compiles because PartialEq is derived (field access is internal to the crate).

---
FINDING 6 (LOW) — Nonce::Clone Residual Copy → FIXED

Clone is removed from Nonce. The struct now derives only Eq. The doc comment on lines 160–162 explicitly states the rationale:

/// `Nonce` intentionally does not implement `Clone` or `Copy`; duplicating
/// nonce bytes increases the number of plaintext copies that must be cleared.

This is the correct approach — documenting the intent prevents future engineers from accidentally re-adding Clone without understanding the implication.

---
FINDING 7 (LOW) — Nonce::Hash Non-Constant-Time → ADDRESSED

The Hash impl now carries a doc comment (lines 199–207) that explicitly tells consumers not to treat it as a security boundary and directs high-assurance deployments toward keyed structures. Removing Hash entirely would make Nonce unusable in replay caches. The warning-in-place approach is correct for this type.

---
One Remaining Note

HybridVerifier and HybridVerified are exported from bcx-crypto but are absent from the prelude in src/lib.rs. Since Verifier: HybridVerifier, any crate that imports Verifier from the prelude and tries to implement it will also need HybridVerifier in scope — which means importing it separately from bcx::crypto. This is not a security issue, but it is a usability gap worth tidying. Consider adding them:

pub use bcx_crypto::{
    AlgorithmPolicy, HybridVerified, HybridVerifier, SignatureAlgorithm,
    SignatureEnvelope, VerificationError, Verifier,
};

---
Summary

┌─────┬──────────┬───────────────────────────────────┬───────────────────────────────────────┐
│  #  │ Severity │              Finding              │                Status                 │
├─────┼──────────┼───────────────────────────────────┼───────────────────────────────────────┤
│ 1   │ HIGH     │ Hybrid verification enforcement   │ Fixed — sealed type + supertrait      │
├─────┼──────────┼───────────────────────────────────┼───────────────────────────────────────┤
│ 2   │ HIGH     │ CI silent skip on missing tools   │ Fixed — unconditional invocation      │
├─────┼──────────┼───────────────────────────────────┼───────────────────────────────────────┤
│ 3   │ MEDIUM   │ u32 as usize platform truncation  │ Fixed — usize::try_from               │
├─────┼──────────┼───────────────────────────────────┼───────────────────────────────────────┤
│ 4   │ MEDIUM   │ SignatureEnvelope Debug raw bytes │ Fixed — manual redacted Debug + test  │
├─────┼──────────┼───────────────────────────────────┼───────────────────────────────────────┤
│ 5   │ MEDIUM   │ ProtocolVersion public fields     │ Fixed — private fields + accessors    │
├─────┼──────────┼───────────────────────────────────┼───────────────────────────────────────┤
│ 6   │ LOW      │ Nonce::Clone zeroize gap          │ Fixed — removed Clone, documented why │
├─────┼──────────┼───────────────────────────────────┼───────────────────────────────────────┤
│ 7   │ LOW      │ Nonce::Hash non-CT comment        │ Addressed — doc warning in place      │
└─────┴──────────┴───────────────────────────────────┴───────────────────────────────────────┘
```
