stacklok / stacklok/toolhive

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

Open
#5,292 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement go proxy security
Dominant language
Go
Stars
2.2k
Forks
300
Avg merge
1d 15h
Merged PRs (30d)
184

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading PR #5288 and the existing ProcessResponse path to understand the streamable-HTTP validation architecture. Then inspect the SSE response processor and the checkMalformedFraming test in the enterprise DAST suite. Done means invalid data frames produce the specified synthetic error event and close the stream, while non-data SSE lines remain unchanged and the weaker guarantee is documented.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
backend, networking, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.