Vulnerability
Two related hardening gaps around signing-key handling for security-sensitive paths:
RequestStateCodec::new (crates/rmcp/src/model/request_state.rs:177) accepts impl Into<Vec<u8>> with no length check. The doc comment recommends "a high-entropy key of at least 32 bytes" but nothing enforces it — a caller can construct a codec with a key as short as b"key" (see the crate's own test at request_state.rs:395, :438, :449, :466, which use 3-byte keys) and it compiles and works normally in production code, not just tests.
- Key material is never zeroized on drop.
RequestStateCodec.key: Box<[u8]> (model/request_state.rs:164) and the signing_key: Vec<u8> used for private_key_jwt client assertions (transport/auth.rs:931, passed into EncodingKey::from_rsa_pem/from_ec_pem at auth.rs:3205-3206) are plain heap buffers with no Drop impl clearing them, and the crate does not depend on zeroize.
Severity
Low (P3) — defense-in-depth. Neither issue is directly triggerable by a network peer; both require a downstream integrator to misuse the API (pick a weak key, or rely on the process not being memory-inspected/core-dumped) rather than an attacker supplying malicious input.
Location
crates/rmcp/src/model/request_state.rs:177 (RequestStateCodec::new, no key-length validation)
crates/rmcp/src/model/request_state.rs:164 (key: Box<[u8]> field, no zeroize)
crates/rmcp/src/transport/auth.rs:931,3205-3206 (signing_key: Vec<u8> for private_key_jwt, no zeroize)
Attack Scenario
A downstream server operator constructs RequestStateCodec::new(b"short") (nothing in the API prevents it). The resulting HMAC-SHA256 key has far less entropy than the documented 32-byte minimum, making offline brute-force/dictionary recovery of the key from observed seal() outputs meaningfully more tractable than intended, which would let an attacker forge requestState blobs (SEP-2322) and bypass the integrity guarantee the codec exists to provide. Separately, because neither the HMAC key nor the JWT signing key is zeroized on drop, either can linger in freed heap memory and surface in a core dump, swap file, or memory-disclosure bug in a way a zeroize-backed type would prevent.
Remediation
- Have
RequestStateCodec::new return a Result (or otherwise reject) keys shorter than a defined minimum (e.g. 16 or 32 bytes), instead of only documenting the requirement.
- Wrap
RequestStateCodec.key and the private_key_jwt signing_key in a zeroize::Zeroizing<Vec<u8>> (or implement ZeroizeOnDrop) so key material is cleared when dropped.
Vulnerability
Two related hardening gaps around signing-key handling for security-sensitive paths:
RequestStateCodec::new(crates/rmcp/src/model/request_state.rs:177) acceptsimpl Into<Vec<u8>>with no length check. The doc comment recommends "a high-entropy key of at least 32 bytes" but nothing enforces it — a caller can construct a codec with a key as short asb"key"(see the crate's own test atrequest_state.rs:395,:438,:449,:466, which use 3-byte keys) and it compiles and works normally in production code, not just tests.RequestStateCodec.key: Box<[u8]>(model/request_state.rs:164) and thesigning_key: Vec<u8>used forprivate_key_jwtclient assertions (transport/auth.rs:931, passed intoEncodingKey::from_rsa_pem/from_ec_pematauth.rs:3205-3206) are plain heap buffers with noDropimpl clearing them, and the crate does not depend onzeroize.Severity
Low (P3) — defense-in-depth. Neither issue is directly triggerable by a network peer; both require a downstream integrator to misuse the API (pick a weak key, or rely on the process not being memory-inspected/core-dumped) rather than an attacker supplying malicious input.
Location
crates/rmcp/src/model/request_state.rs:177(RequestStateCodec::new, no key-length validation)crates/rmcp/src/model/request_state.rs:164(key: Box<[u8]>field, no zeroize)crates/rmcp/src/transport/auth.rs:931,3205-3206(signing_key: Vec<u8>forprivate_key_jwt, no zeroize)Attack Scenario
A downstream server operator constructs
RequestStateCodec::new(b"short")(nothing in the API prevents it). The resulting HMAC-SHA256 key has far less entropy than the documented 32-byte minimum, making offline brute-force/dictionary recovery of the key from observedseal()outputs meaningfully more tractable than intended, which would let an attacker forgerequestStateblobs (SEP-2322) and bypass the integrity guarantee the codec exists to provide. Separately, because neither the HMAC key nor the JWT signing key is zeroized on drop, either can linger in freed heap memory and surface in a core dump, swap file, or memory-disclosure bug in a way azeroize-backed type would prevent.Remediation
RequestStateCodec::newreturn aResult(or otherwise reject) keys shorter than a defined minimum (e.g. 16 or 32 bytes), instead of only documenting the requirement.RequestStateCodec.keyand theprivate_key_jwtsigning_keyin azeroize::Zeroizing<Vec<u8>>(or implementZeroizeOnDrop) so key material is cleared when dropped.