Repository navigation
[Vulnerability] modelcontextprotocol/rust-sdk: Message Validation Bypass / Protocol Violation #190
Description
Potential Security Vulnerability Detected
Repository: modelcontextprotocol/rust-sdk
Commit: e3b4e32
Author: ump45nose
Date: 2026-10-14T13:32:00Z
Commit Message
fix(model): reject malformed JSON-RPC messages with conflicting result and error fields (#1283) (#1287)
* fix(model): reject malformed JSON-RPC messages with conflicting result and error fields (#1283)
* fix(model): preserve Deserialize<'de> bounds for JsonRpcMessage
Keep the public Deserialize impl equivalent to the previous derive
(Deserialize<'de> bounds, Noti type param) so cargo public-api --deny
removed passes, while still rejecting conflicting result/error fields.
* fix(model): preserve borrowed JSON-RPC payload deserialization
Reject conflicting result/error shapes without forcing an owned
serde_json::Value intermediate for from_str/from_slice. Use serde_json's
RawValue newtype token so Deserialize<'de> payload types can keep
borrows into the original input, with an owned fallback for from_value.
Adds regression coverage for generic borrowed request/response payloads.
* fix(model): alias JsonRpcMessageVisitor PhantomData for clippy
Factor the PhantomData<fn() -> (Req, Resp, Noti)> marker into a type
alias so clippy::type_complexity no longer fails CI under -D warnings.
* fix(model): preserve JSON-RPC deserialization in untagged wrappers
* fix(model): use borrowing-compatible buffered message parsing
* fix(model): preserve public deserializer signature
* refactor(model): remove masked JSON-RPC shape errors
---------
Co-authored-by: ump45nose <>
Pull Request
PR: #1287 - fix(model): reject malformed JSON-RPC messages with conflicting result and error fields (#1283)
Labels: T-core
Description:
Summary
fix(model): reject malformed JSON-RPC messages with conflicting result and error fields (#1283)
- Use one borrowing-compatible buffered deserializer for direct messages and single/batch outer untagged wrappers.
- Remove the private serde_json RawValue token, its custom visitors, and the redundant full-Value parsing path.
- Keep field-presence validation: conflicting result/error members are rejected even when one or both values are null. A lone null result remains supported for gene...
Analysis
Vulnerability Type: Message Validation Bypass / Protocol Violation
Severity: High
Description
The JSON-RPC 2.0 specification requires that responses contain either a 'result' or 'error' field, but not both. Before this patch, the derived Deserialize implementation silently accepted malformed messages with conflicting result and error fields, potentially allowing attackers to inject conflicting data that could be misinterpreted by downstream handlers. The patch adds explicit field-presence validation during deserialization to reject such malformed messages.
Affected Code
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(untagged)]
pub enum JsonRpcMessage<Req, Resp, Noti> {
Request(JsonRpcRequest<Req>),
Response(JsonRpcResponse<Resp>),
Notification(JsonRpcNotification<Noti>),
Error(JsonRpcError),
}
Proof of Concept
const malformedMessage = {
"jsonrpc": "2.0",
"id": 1,
"result": { "content": [] },
"error": { "code": -32603, "message": "injected error" }
};
// Before patch: serde_json::from_value::<JsonRpcMessage>(malformedMessage) succeeds, silently dropping one field
// After patch: deserialization fails with "unrecognized JSON-RPC message" error
// Attack scenario: Attacker sends response with both result and error; vulnerable code may process the wrong field based on which was encountered first
This issue was automatically created by Vulnerability Spoiler Alert.
Detected at: 2026-10-14T16:18:22.835Z
Activity