Skip to content

Message Smuggling via Case-Insensitive JSON Unmarshalling #805

@slimslenderslacks

Description

@slimslenderslacks

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

  1. internal/jsonrpc2/messages.go - DecodeMessage() function (lines 174-207)
  2. 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:

  1. Authorization middleware checks params.name = "greet" ✅ (allowed)
  2. Go's json.Unmarshal() matches both "name" and "Name" to the same struct field
  3. The last field ("Name": "secretTool") wins
  4. When re-serialized or accessed, params.Name = "secretTool"
  5. 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, &params)

      fmt.Println(params.Name) // Output: "smuggled" (last field wins!)
  }

Expected Behavior

The SDK should:

  1. Reject JSON messages with duplicate keys (regardless of case)
  2. Enforce case-sensitive field name matching per JSON-RPC 2.0 spec
  3. 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

Suggested Fix

Implement strict JSON parsing that:

  1. Detects duplicate keys with case-insensitive comparison
  2. Validates field names match struct tags exactly (case-sensitive)
  3. Uses json.Decoder.DisallowUnknownFields() for additional safety
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0Critical: core functionality failures or high-severity security

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions