Skip to content

feat(proxy): validate JSON-RPC frames in SSE event stream #5292

@amirejaz

Description

@amirejaz

Background

PR #5288 adds upstream JSON-RPC response validation for the streamable-HTTP transport. SSE is explicitly out of scope because the fix architecture (buffer full body → validate → rewrite or pass through) doesn't work for a streaming event format.

The gap

When an SSE-based MCP upstream sends a structurally invalid JSON-RPC frame (data: line without jsonrpc:"2.0", wrong id type, etc.), the transparent proxy forwards it unchanged. Clients receive the malformed event and may crash, misparse it, or treat it as a legitimate response.

Why it's harder than streamable-HTTP

For streamable-HTTP, ProcessResponse buffers the entire HTTP response body before any bytes reach the client, so it can atomically reject and rewrite to 502.

For SSE:

  • The 200 OK and Content-Type: text/event-stream headers are committed before any events arrive. There is no way to rewrite the status code for a malformed event mid-stream.
  • A fix needs a per-event streaming interceptor that wraps the SSE body reader, parses each data: line as it arrives, validates the JSON-RPC content, and either synthesizes an error event or closes the stream cleanly on the first invalid frame.
  • "Close the stream" is the strongest guarantee available — unlike streamable-HTTP, the proxy cannot return a structured 502 for an individual bad event.

Proposed scope

A follow-up to #5288 scoped to the SSE response processor:

  • Wrap the SSE body reader with a per-event interceptor
  • On an invalid data: JSON-RPC frame: write a synthetic data: {"jsonrpc":"2.0","id":null,"error":{"code":-32000,"message":"Invalid upstream JSON-RPC frame"}}\n\n event and close the stream
  • Leave non-data: SSE lines (comments, event:, retry:) untouched
  • The guarantee is weaker than streamable-HTTP (client receives the error event, not a 502) — document this explicitly

Context

Found via DAST adversarial testing (checkMalformedFraming in the enterprise DAST suite). The test currently documents this as a known finding for the SSE transport.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestgoPull requests that update go codeproxysecurity

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions