Repository navigation
fix(agentmesh-mcp): prevent Rust MCP signature field reframing #4199
Description
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
- Use one signing key and a fixed
timestamp_secs. - Sign payload
x:ywith noncen. - Before sending the original envelope, change its fields to nonce
n:xand payloady, retaining the original signature and timestamp. - Submit the modified envelope to a verifier using the same key and a fresh/independent replay store.
- Observe that the MAC input remains
{timestamp_secs}:n:x:yand 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:yton:x/yreframing 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
docs: scope MCP v2 signing to Python
abhay-2108 commentedon Oct 2, 2026
Hey Ricky Gummadi (@Ricky-G), is this PR still open. If yes, I would like to work on it.
incr3d1ble commentedon Oct 15, 2026
I'd like to take this on. Planned approach, following the completion criteria in this issue:
agent_os.mcp_message_signer) and the RustMcpMessageSigner, 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|.n/x:y→n:x/yreframing is rejected, with no legacy fallback.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).
fix(mcp): versioned injective canonical signing shared across Python …
fix(agentmesh-mcp): adopt the shared agent-os v2 canonical encoding