Skip to content

fix(agentmesh-mcp): prevent Rust MCP signature field reframing #4199

Open

Description

@Ricky-G

Package

agent-mcp-governance (agent-governance-rust/agentmesh-mcp, Rust crate agentmesh-mcp)

Description

Blocking security follow-up for Rust MCP message signing. The Rust McpMessageSigner in agentmesh-mcp/src/mcp/signing.rs authenticates the ambiguous string "{timestamp_secs}:{nonce}:{payload}". Since both nonce and payload may contain :, distinct signed field tuples can produce the same HMAC input. The Rust envelope also uses a different representation from the Python v2 signer, so a Rust verifier on main cannot verify Python v2 signatures (or vice versa).

For one fixed timestamp, these fields collide:

Nonce Payload Current canonical input
n x:y {timestamp_secs}:n:x:y
n:x y {timestamp_secs}:n:x:y

An attacker can take a valid envelope for the first tuple, change the nonce and payload to the second tuple, and retain the original signature. A receiver with an independent nonce store can accept the reframed envelope under the alternate nonce.

How does this impact your work?

The Rust signer does not currently provide field-boundary integrity and does not interoperate with the Python v2 signature format. This is a blocking security and interoperability follow-up: do not claim Rust field-reframing protection or Python/Rust signed-envelope compatibility until this issue is resolved.

Timeline

Blocking follow-up. Resolve before claiming cross-SDK MCP signature security or interoperability.

Steps to Reproduce

  1. Use one signing key and a fixed timestamp_secs.
  2. Sign payload x:y with nonce n.
  3. Before sending the original envelope, change its fields to nonce n:x and payload y, retaining the original signature and timestamp.
  4. Submit the modified envelope to a verifier using the same key and a fresh/independent replay store.
  5. Observe that the MAC input remains {timestamp_secs}:n:x:y and the field-reframed message verifies under the current colon-joined encoding.

Environment

Repository: microsoft/agent-governance-toolkit, main
Crate: agent-governance-rust/agentmesh-mcp
Implementation: src/mcp/signing.rs

Logs / Error Output

No error is produced: the verifier accepts the reframed message because the HMAC inputs are identical.

Code of Conduct

  • I agree to follow the Microsoft Open Source Code of Conduct

Required completion criteria

  • Define and document one versioned, injective canonical signing representation shared by Python and Rust, including timestamp precision and sender/null semantics.
  • Add the same fixed canonical-byte and HMAC test vectors to both SDKs, covering separators, JSON-looking text, Unicode, null versus empty sender, and full timestamp precision; verify cross-SDK sign/verify in both directions.
  • Add a regression proving the n / x:y to n:x / y reframing is rejected without legacy fallback.
  • Document the coordinated breaking migration and ensure the Rust wire envelope representation is compatible with the shared vectors.
  • Preserve signature-before-replay-store behavior and replay protection.

This follow-up was requested during review of #4132 by Prayag (@prayagupa) and MohammadHaroonAbuomar. That PR explicitly scopes its v2 change to Python and does not resolve this Rust issue.

Activity

  1. abhay-2108 commented on Oct 2, 2026

    @abhay-2108

    Hey Ricky Gummadi (@Ricky-G), is this PR still open. If yes, I would like to work on it.

  2. incr3d1ble commented on Oct 15, 2026

    @incr3d1ble

    I'd like to take this on. Planned approach, following the completion criteria in this issue:

    1. Canonical encoding — a versioned, injective representation shared by the Python v2 signer (agent_os.mcp_message_signer) and the Rust McpMessageSigner, using length-prefixed fields (e.g. v1|<ts>|<len>:<nonce>|<len>:<payload>), which is injective without escaping and unambiguous for any nonce/payload containing : or |.
    2. Shared test vectors — identical fixed HMAC vectors added to both SDKs, covering separator characters, JSON-looking text, Unicode, null vs empty sender, and full timestamp precision, plus cross-SDK sign/verify in both directions.
    3. Regression test — proves the n/x:y → n:x/y reframing is rejected, with no legacy fallback.
    4. Migration docs — a coordinated breaking-change note for both SDK envelopes.

    I'll open the PR shortly. Abhay Tiwari (@abhay-2108) — I don't see a PR from you and this issue has been open since Oct 2, so I'm proceeding unless you're mid-flight (happy to coordinate).

  3. added 2 commits that reference this issue on Oct 15, 2026
    8e9a593
    ab0ba45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions