-
Notifications
You must be signed in to change notification settings - Fork 362
Description
Summary
The go-sdk is vulnerable to a protocol parser differential attack that allows message smuggling through case-variant JSON fields. This vulnerability exploits
Go's case-insensitive JSON unmarshalling behavior, which violates the JSON-RPC 2.0 specification's requirement for case-sensitive field matching.
Impact
An attacker can bypass authorization checks and invoke unauthorized MCP methods by sending messages with duplicate fields using different cases. The last
field wins during parsing, but validation/authorization occurs on the first field.
Severity: High - Allows unauthorized access to restricted tools, prompts, and resources
Vulnerability Details
Root Cause
The SDK uses Go's standard json.Unmarshal() which performs case-insensitive field matching. When a JSON message contains duplicate keys with different cases
(e.g., both "name" and "Name"), Go matches both to the same struct field, and the last occurrence wins.
This violates the JSON-RPC 2.0 specification which requires exact, case-sensitive field matching.
Affected Components
- internal/jsonrpc2/messages.go - DecodeMessage() function (lines 174-207)
- mcp/shared.go - newMethodInfo() function's unmarshalParams closure (lines 283-308)
Attack Scenario
An attacker can smuggle unauthorized method names through case variants:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "greet", // Lowercase - passes authorization
"Name": "secretTool", // Uppercase - smuggled, overwrites after parsing
"arguments": {"target": "sensitive_data"}
}
}
What happens:
- Authorization middleware checks params.name = "greet" ✅ (allowed)
- Go's json.Unmarshal() matches both "name" and "Name" to the same struct field
- The last field ("Name": "secretTool") wins
- When re-serialized or accessed, params.Name = "secretTool"
- Unauthorized tool secretTool is executed ❌
Affected Message Types
- tools/call - name field can be smuggled via Name, NAME, etc.
- prompts/get - name field can be smuggled
- resources/read - uri field can be smuggled via URI, Uri, etc.
- Any params struct with json-tagged fields
Reproduction
package main
import (
"encoding/json"
"fmt"
)
type Params struct {
Name string `json:"name"`
}
func main() {
// Attack payload with duplicate keys
jsonData := []byte(`{"name": "allowed", "Name": "smuggled"}`)
var params Params
json.Unmarshal(jsonData, ¶ms)
fmt.Println(params.Name) // Output: "smuggled" (last field wins!)
}
Expected Behavior
The SDK should:
- Reject JSON messages with duplicate keys (regardless of case)
- Enforce case-sensitive field name matching per JSON-RPC 2.0 spec
- Return a clear error for non-compliant messages
Actual Behavior
The SDK silently accepts messages with case-variant duplicate fields, allowing the last occurrence to overwrite earlier values without any validation error.
Security Considerations
This vulnerability affects:
- ✅ Any MCP server using go-sdk with authorization/validation middleware
- ✅ Any MCP gateway that forwards/transforms messages after parsing
- ✅ Any system that re-serializes parsed params for logging or forwarding
References
- JSON-RPC 2.0 Specification: https://www.jsonrpc.org/specification
- Go json package behavior: https://pkg.go.dev/encoding/json#Unmarshal
- Related CVE patterns: Parser differential attacks, message smuggling
Suggested Fix
Implement strict JSON parsing that:
- Detects duplicate keys with case-insensitive comparison
- Validates field names match struct tags exactly (case-sensitive)
- Uses json.Decoder.DisallowUnknownFields() for additional safety
- Fails fast with clear error messages for non-compliant JSON
Note: This issue is being actively remediated. A PR with a complete fix including strict parsing implementation and comprehensive test coverage will follow.