#4879·higress

mcp-session: SSE fragment cache dropped at endStream + unbounded cache growth

Author: johnlanniCreated Sep 29, 2026Updated Sep 29, 2026

Background

While reviewing #4878 (the SSE fragment write-back fix for #4651), two adjacent defects were identified in plugins/golang-filter/mcp-session/filter.go's SSE upstream path. They are pre-existing (present since the buffering logic landed) and deliberately out of #4878's scope to keep that PR single-concern.

1. endStream silently drops a non-empty fragment cache

encodeDataFromSSEUpstream's buffer-and-retry branch (filter.go:330-335) ignores endStream: it caches combinedData and calls buffer.Reset() unconditionally, and there is no terminal flush anywhere — EncodeData at filter.go:260-263 forces api.Continue on end-of-stream. If the upstream closes before a parseable endpoint event completes (final chunk missing the trailing blank line), the cached data is dropped and the client receives nothing — the exact #4651 symptom, one variant later.

Fix direction: when endStream is true and the cache (plus current chunk) is non-empty, flush the assembled bytes to the buffer instead of resetting, so the client receives whatever the upstream sent.

2. Unbounded fragment cache with O(n²) reassembly

The retry path has no cap on cachedResponseBody and reassembles by full-string concatenation on every chunk. A misbehaving or malicious SSE upstream that never terminates an event can grow the filter's Go heap without bound (Envoy's buffer limit cannot police it because each chunk is drained into the filter), with quadratic copying cost on top.

Fix direction: cap the cache at a configurable limit (defaulting to something sane, e.g. 1 MiB); on overflow, pass the data through unmodified and log (fail-open to passthrough rather than dropping).

Both are happy to be taken together or separately. Nominating as follow-ups to #4878.