Skip to content

request-state and JWT signing key material lacks minimum-length enforcement and zeroization #1161

Description

@bug-ops

Vulnerability

Two related hardening gaps around signing-key handling for security-sensitive paths:

  1. 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.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is not working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions