{"id":"GHSA-4gph-2hhr-5mwg","aliases":["GO-2026-5121"],"url":"https://o3.security/vulnerability/GHSA-4gph-2hhr-5mwg","summary":"Envoy AI Proxy - MCP Message Smuggling Vulnerability","details":"Envoy AI Gateway was found to be affected by a protocol parser differential vulnerability due to improper implementation of the JSON-RPC 2.0 specification. Such differential causes a MCP message alteration, potentially causing a bypass of security controls in a multi-layered architecture.\n\nAccording to the JSON RPC Spec used by Model Context Protocol, JSON RPC should be case sensitive https://www.jsonrpc.org/specification\n\n```\n[...]\nAll member names exchanged between the Client and the Server that are considered for matching of any kind should be considered to be case-sensitive. The terms function, method, and procedure can be assumed to be interchangeable.\n```\n\nThe AI Gateway is accepting and processing case-variant fields that compliant MCP implementations correctly ignore. Crucially, Envoy does not just \"pass through\" the message by acting as a transparent proxy, it alters the traffic, allowing smuggling of unwanted requests. \n\nThe following steps represent the incoming message alteration:\n1. **Incoming MCP Message**:\n\n```\n{\n    id: 1,\n    jsonrpc: \"2.0\",\n    method: \"tools/call\",\n    params: {\n        name: \"backend__greet\",\n        Name: \"backend__secretTool\",\n        arguments: {\n            name: \"World!\"\n        },\n        Arguments: {\n            name: \"Exploit\"\n        }\n    }\n}\n```\n\n2. **Parses** the request, picking the non-standard `Name` field over the authorized `name` field due to internal case-insentitive parsing by libraries such as `modelcontextprotocol/go-sdk/jsonrpc` and `github.com/bytedance/sonic`\n3.  **Overwrites** the authorized \"backend__greet\" value from the valid `name` field with the malicious value from the `Name` field\n4.  **Normalizes** the injected \"backend__secretTool\" value (from the invalid `Name` field)\n5.  **Re-serializes** the request into a new, valid MCP JRPC payload (`{\"name\": \"backend__secretTool\"}`) and forwards it upstream\n\nThis \"smuggling\" effect means Envoy actively transforms a request that might have been checked by any prior MCP-compliant implementation into a request that is **valid and altered** (from the perspective of the upstream backend), effectively introducing protocol modifications that may allow bypassing any prior authorization layer. \n\n## Root Cause Analysis\n\nThe root cause is a parser differential combined with serialization quirk typical of Go-based JSON parsers:\n\n1. **Case-Insensitive Unmarshaling** - When parsing a JSON key into a struct field, Go's `json.Unmarshal` looks for an a case-insensitive match. Go matches \"Name\" to the struct tag `json:\"name\"`. If `\"name\": \"safe\"` is also present, the last key processed wins, allowing an attacker to inject a different tool name when the MCP message reaches the Go-SDK parsing\n2. **Strict Struct-Tag Marshaling** - When converting a struct back to JSON, Go always uses the exact key specified in the Struct `json:\"...\"` tag. Consequently, an overwritten value from a cased field could be stored in a proper lowercased parameter, later processed by a spec-compliant MCP message receiver\n\nThe vulnerability involves the usage of the `jsonrpc` module of `github.com/modelcontextprotocol/go-sdk`, which is not following the MCP mandated JSON RPC spec for messages parsing, using case insensitive matching.\n\nThe non-compliant parsing primitive is `jsonrpc.DecodeMessage`, it is widely used to parse the incoming MCP messages.\nSee at:\n- `ai-gateway/internal/mcpproxy/handlers.go:242,739`\n- `ai-gateway/internal/mcpproxy/mcpproxy.go:303`\n- `ai-gateway/internal/mcpproxy/session.go:409`\n- `ai-gateway/internal/mcpproxy/sse.go:88`\n\nConsequently, internals relying on objects parsed with the cited primitive are using case-insentive parsing, also subject to Unicode to ASCII Folding. \n\nFurthermore, the internal logic is also relying on the `internal/json` package, acting as a wrapper around `github.com/bytedance/sonic` (Sonic), a high-performance JSON library that defaults to loose, case-insensitive unmarshalling.\n\n**File**: `internal/json/json.go`\n```go\nimport (\n\tsonicjson \"github.com/bytedance/sonic\"\n)\nvar (\n\tUnmarshal = sonicjson.ConfigDefault.Unmarshal\n    Marshal   = sonicjson.ConfigDefault.Marshal\n)\n``` \n\nIn combination with the above mentioned spec departure, the message MCP message reconstruction logic is causing an alteration of the protocol calls passing through the gateway.\n\n### Example Vulnerable Data Flow - tools/call\n\nThe alteration occurs in `ai-gateway/internal/mcpproxy/handlers.go:180` during the processing of a function `servePOST`.\n\nAs an example, we can focus on the processing of incoming MCP `tools/call` requests.\n\nWhen the gateway receives a JSON-RPC request, it executes `servePOST` function, which then calls `jsonrpc.DecodeMessage` to parse the body of the bytes read from the request.\n\nSee at `ai-gateway/internal/mcpproxy/handlers.go:235-242`\n```go\n...\n\tbody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\terrType = metrics.MCPErrorInternal\n\t\tonErrorResponse(w, http.StatusBadRequest, err.Error())\n\t\treturn\n\t}\n\n\trawMsg, err := jsonrpc.DecodeMessage(body)\n    ...\n```\n\nIf the request method is `tools/call`, the following code is executed at `ai-gateway/internal/mcpproxy/handlers.go:366`\n\n```go\n\t\tcase \"tools/call\":\n\t\t\tparams = &mcp.CallToolParams{}\n\t\t\tspan, err = parseParamsAndMaybeStartSpan(ctx, m, msg, params, r.Header)\n\t\t\tif err != nil {\n\t\t\t\terrType = metrics.MCPErrorInvalidParam\n\t\t\t\tm.l.Error(\"Failed to unmarshal params\", slog.String(\"method\", msg.Method), slog.String(\"error\", err.Error()))\n\t\t\t\tonErrorResponse(w, http.StatusBadRequest, \"invalid params\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\terr = m.handleToolCallRequest(ctx, s, w, msg, params.(*mcp.CallToolParams), span, r)\n```\n\nAt this point, the `params` object is a `mcp.CallToolParams` struct incorrectly parsed by Anthropic's `jsonrpc.DecodeMessage` as case insentitive object. The `params` object is then passed to `handleToolCallRequest` function, which will execute the tool call.\n\nSee at definition of `handleToolCallRequest` at `ai-gateway/internal/mcpproxy/handlers.go:638`\n```go\nfunc (m *mcpRequestContext) handleToolCallRequest(ctx context.Context, s *session, w http.ResponseWriter, req *jsonrpc.Request, p *mcp.CallToolParams, span tracingapi.MCPSpan, r *http.Request) error {\n\n\t// ... REDACTED CODE TO Enforce authentication and authorizationif required by the route ...\n\n\t// Send the request to the MCP backend listener.\n\tp.Name = toolName\n\tparam, _ := json.Marshal(p)\n\tif m.l.Enabled(ctx, slog.LevelDebug) {\n\t\tlogger := m.l.With(slog.String(\"tool\", p.Name), slog.Any(\"session\", cse))\n\t\tlogger.Debug(\"Routing to backend\")\n\t}\n\tif span != nil {\n\t\tspan.RecordRouteToBackend(backend.Name, string(cse.sessionID), false)\n\t}\n\treq.Params = param\n\treturn m.invokeAndProxyResponse(ctx, s, w, backend, cse, req)\n}\n```\n\nIn the code pattern above, the final alteration of the MCP message happens. The function is using as `json` parser the wrapper around `sonic` (`ai-gateway/internal/json`), which is a case-insensitive parser, to reserialize the parameter struct with `json.Marshal`.\n\nWhen the struct is **re-marshaled**, it uses the exact casing defined in the struct's JSON tag. This allows an attacker to \"smuggle\" a malicious value through a non-standard key name, bypassing case-sensitive filters on previous stages of the request handling by other MCP-compliant components.\n\nIn conclusion, the described MCP message alteration is ensuring that the final proxied version by Envoy-AI Gateway will become \"canonical\" and delivered upstream with smuggled malicious values.\n\n### Other Message Types\n\nThe Parser Differential is not limited to `tools/call`. It is a pervasive design issue caused by the Go SDK and Sonic manipulation of MCP Messages.\n\nList of smuggling sinks:\n- `tools/call` \n- `prompts/get`\n- `resources/read`\n\nIn conclusion, the reported smuggling problem is systemic in the AI Proxy.\n\n## Proof of Concept\n\nPayload:\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"backend__greet\",      // SAFE: Validated & Allowed by Edge Proxy\n    \"Name\": \"backend__secretTool\", // UNSAFE: By-passes Edge authz checks, altered by Envoy and delivered upstream\n    \"arguments\": {\"name\": \"Exploit\"}\n  },\n  \"id\": 1\n}\n```\n\nScenario:\n\n<img width=\"1588\" height=\"407\" alt=\"image\" src=\"https://github.com/user-attachments/assets/326731f0-515c-489b-87f3-b386b1e1831d\" />\n\nThe full PoC can be downloaded as `envoy-security@googlegroups.com` at: https://doyensec.sendsafely.com/receive/?thread=3574-4CPK&packageCode=lAXsPv8NK0tR0VTEXno9DZjFB5Ph3eKaSER6rpiirvk#keyCode=R_jMV3ENlORCmzkka3jwupPgsUq-TqNOAoizUqzlWHg\n\nPrerequisites are having installed: Minikube, Docker and Helm.\n\nIn order to reproduce the scenario, it is sufficient to run the described stack with the commands below:\n```\nchmod +x full_PoC.sh\n./full_PoC.sh\n```\n\nThe PoC will:\n1. Start a MCP-cmpliant downstream proxy that should block requests to `backend__secretTool`\n2. Start the Envoy AI Gateway and configure it as basic router\n3. Start an upstream MCP server exposing `backend__secretTool` and `backend_greet`\n4. Send MCP `tools/call` requests to the downstream proxy to demonstrate the vulnerability\n\nIt is also possible to inspect the full traffic proccessing logs at `PoC/logs/`.\n\nThe example below shows the final PoC smuggled message received and executed by the upstream MCP Server:\n\n```\ncat PoC/logs/server_container.log\n\n...[REDACTED]...\nINFO:     10.244.0.6:33404 - \"POST /mcp HTTP/1.1\" 200 OK\n[2026-02-11T14:57:11.055237] ← PROCESSED MCP REQ: initialize\nINFO:     10.244.0.6:33404 - \"POST /mcp HTTP/1.1\" 202 Accepted\nINFO:     10.244.0.6:33420 - \"POST /mcp HTTP/1.1\" 200 OK\n[2026-02-11T14:57:11.334681] → RECEIVED MCP REQ: tools/list\n[2026-02-11T14:57:11.334924]   Payload: {\n  \"method\": \"tools/list\",\n  \"params\": null\n}\n[2026-02-11T14:57:11.335298] ← PROCESSED MCP REQ: tools/list\n[2026-02-11T14:57:11.336396] → RECEIVED MCP REQ: tools/call\n[2026-02-11T14:57:11.336467]   Payload: {\n  \"task\": null,\n  \"meta\": null,\n  \"name\": \"greet\",\n  \"arguments\": {\n    \"name\": \"User\"\n  }\n}\n[2026-02-11T14:57:11.336665] greet called with name=User\n[2026-02-11T14:57:11.336935] ← PROCESSED MCP REQ: tools/call\nINFO:     10.244.0.6:33420 - \"POST /mcp HTTP/1.1\" 200 OK\n[2026-02-11T14:57:11.552185] → RECEIVED MCP REQ: tools/call\n[2026-02-11T14:57:11.552341]   Payload: {\n  \"task\": null,\n  \"meta\": null,\n  \"name\": \"secretTool\",\n  \"arguments\": {\n    \"name\": \"ExploitUser\"\n  }\n}\n[2026-02-11T14:57:11.552422] LEAK: secretTool called with name=ExploitUser\n[2026-02-11T14:57:11.552483] ← PROCESSED MCP REQ: tools/call\n```\n\nWhile below is reported the log of the downstream compliant proxy that allowed the message as compliant `greet` command before it was smuggled by Envoy AI Proxy:\n\n```\n2026-02-11 14:57:11,408 - downstream-proxy - INFO - Incoming request: method='tools/call' params={'name': 'backend__secretTool', 'arguments': {'name': 'Hacker'}} jsonrpc='2.0' id=3 [tool: backend__secretTool]\n2026-02-11 14:57:11,408 - downstream-proxy - INFO - Inspecting tools/call for tool: backend__secretTool\n2026-02-11 14:57:11,408 - downstream-proxy - WARNING - BLOCKED: Attempt to call restricted tool 'backend__secretTool'\nINFO:     127.0.0.1:33768 - \"POST /mcp HTTP/1.1\" 403 Forbidden\n2026-02-11 14:57:11,514 - downstream-proxy - INFO - Incoming request: method='tools/call' params={'name': 'backend__greet', 'arguments': {'name': 'ExploitUser'}, 'Name': 'backend__secretTool'} jsonrpc='2.0' id=3 [tool: backend__greet]\n2026-02-11 14:57:11,514 - downstream-proxy - INFO - Inspecting tools/call for tool: backend__greet\n2026-02-11 14:57:11,514 - downstream-proxy - INFO - ALLOWED: Tool 'backend__greet' passed policy check.\n2026-02-11 14:57:11,578 - httpx - INFO - HTTP Request: POST http://envoy-default-aigw-run-85f8cf28.envoy-gateway-system.svc.cluster.local:1975/mcp \"HTTP/1.1 200 OK\"\nINFO:     127.0.0.1:33780 - \"POST /mcp HTTP/1.1\" 200 OK\n```\n\n## Impact\nThis vulnerability makes downstream authorization layers ineffective when Envoy AI Gateway is used as a router. By altering the message content, Envoy effectively acts as a \"Confused Deputy\" that:\n1.  **Trusted Input**: Takes input trusted by the other MCP-compliant actors \n2.  **Malicious Transformation**: Converts it into a totally different, but compliant, MCP message. Violating the transparency nature of proxies and altering a valid request into a malicious one\n3.  **Trusted Output**: Sends it off as a valid request to the upstream backend\n\nAuthentication and Authorization guarantees provided by prior actors or MCP manipulations are nullified because the original message **is not** the message the target Backend receives.\n\n## Remediation\n\n1. Strict Protocol Compliance\nEnvoy AI Gateway must enforce **Strict Case Sensitivity** during JSON unmarshalling to comply with JSON-RPC 2.0.\n\n2. Reject Unknown Fields In Protocol Reserved Areas\nEnable `DisallowUnknownFields`.\n\n3. Preserve Integrity\nIf Envoy acts as a transparent router, it should avoid unnecessary re-serialization of the payload body unless modification is explicitly required and safe. If normalization is required, it must be lossless and strictly validated.\n\nIt should be highlighted that after our root-cause analysis, `github.com/modelcontextprotocol/go-sdk` is ultimately responsible for the initial part the issue. As a result, we would expect the maintainers to fix the vulnerability upstream.\n\nAs a general recommendation, Envoy AI Gateway should use a JSON RPC parser and subsequent JSON parsing strategies in `sonic` which are following the SPEC to prevent the differentials, forcing the case sensitive matching on MCP messages. \n\nDoyensec has already reported the Go-SDK violation of the JSON RPC Specification via Hackerone.","published":"2026-05-19T16:18:14Z","modified":"2026-06-25T18:56:38.422241807Z","cvss":null,"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Go","name":"github.com/envoyproxy/ai-gateway","fixedVersion":"0.6.0"}],"fix":null,"references":[{"type":"WEB","url":"https://github.com/envoyproxy/ai-gateway/security/advisories/GHSA-4gph-2hhr-5mwg"},{"type":"PACKAGE","url":"https://github.com/envoyproxy/ai-gateway"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-06-25T18:56:38.422241807Z"}}