Repository navigation
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.
Activity
findleyr commentedon Feb 18, 2026
Thanks, and I agree we should be strict. I wonder if we can/should use a drop-in replacement for encoding/json that supports case-sensitivity, to avoid the complexity and performance overhead of this fix.
I am however significantly concerned about the repercussions of
json.Decoder.DisallowUnknownFields(), which seems like an extra precaution not directly related to this threat vector.slimslenderslacks commentedon Feb 18, 2026
@findleyr ya, I think a drop-in replacement that support case-sensitivity would be better actually. Feel free to close this PR, but I can copy in some tests that if that would help.
maciej-kisiel commentedon Feb 18, 2026
Based on my research, https://github.com/goccy/go-json is the best candidate for the drop-in replacement, evaluated based on whether it's still actively supported and the popularity. In the future we should likely get back to
encoding/json/v2which is under development, but will not be available sooner than in Go 1.27 and possibly even later.maciej-kisiel commentedon Feb 18, 2026
Correction: https://github.com/goccy/go-json doesn't actually support case sensitive matching. I this case, I believe we're left with newer, but less popular https://github.com/segmentio/encoding and already archived, but more popular https://github.com/json-iterator/go. In this case I think I would opt for the former, given it's still maintained and has smaller dependency set. I created #807 to address this more holistically across the codebase.
all: use case-sensitive JSON unmarshaling (#807)
all: use case-sensitive JSON unmarshaling (#807)
maciej-kisiel commentedon Feb 18, 2026
The PR has been merged and cherry-picked to a new https://github.com/modelcontextprotocol/go-sdk/releases/tag/v1.3.1 release. This should address the issue.
dependency: update modelcontextprotocol/go-sdk to v1.3.1
dependency: update modelcontextprotocol/go-sdk to v1.3.1 (#6127)
dependency: update modelcontextprotocol/go-sdk to v1.3.1 (#6127)