highCVE-2026-53965Aug 19, 2026

CVE-2026-53965: mcp/sdk HttpTransport Unbounded SSE Buffer Memory Exhaustion

Shubham Kandhare
Security Engagement Manager, SecureLayer7

The MCP PHP SDK HTTP client transport appends Server-Sent Events data to an in-memory buffer with no size limit, letting any server crash the client process by streaming bytes without ever sending…

Packagemcp/sdk
Ecosystemcomposer
Affected>= 0.5.0, < 0.7.1
Fixed in0.7.1
CVE-2026-53965: mcp/sdk HttpTransport Unbounded SSE Buffer Memory Exhaustion

The problem

In mcp/sdk versions 0.5.0 through 0.7.0, the HttpTransport class reads SSE response chunks in a polling loop and concatenates each 4 KiB read directly onto $this->sseBuffer with no length guard.

The buffer is only drained when the SSE event delimiter \n\n appears. A server that withholds that delimiter while streaming data forces the buffer to grow on every tick until the PHP process hits its memory_limit and crashes with a fatal OOM error. The attacker surface is any server the client connects to over HTTP, including a man-in-the-middle on a plaintext endpoint.

Proof of concept

A working proof-of-concept for CVE-2026-53965 in mcp/sdk, with the exact payload below.

php
<?php
// Minimal reproduction: inject a delimiter-free stream into the unpatched transport.
// The transport's processSSEStream() appends each 4096-byte read to sseBuffer.
// With no "\n\n" in the stream, the buffer never drains.

// Start a rogue SSE server that streams garbage bytes with no "\n\n" delimiter:
// php -r "header('Content-Type: text/event-stream'); while(true) { echo str_repeat('A', 4096); flush(); }"

// Or drive the sink directly with the PoC from the advisory:
ini_set('memory_limit', '256M');
$SIZE = 400 * 1024 * 1024; // 400 MB, NO \n\n

// FloodStream: a PSR-7 StreamInterface that yields $SIZE bytes of 'A' with no \n\n.
// Inject it into HttpTransport via reflection, then pump processSSEStream() in a loop.
// Result:
// PHP Fatal error: Allowed memory size of 268435456 bytes exhausted
//   (tried to allocate 264241184 bytes)
//   in vendor/mcp/sdk/src/Client/Transport/HttpTransport.php on line 203

The root cause is CWE-770 (Allocation of Resources Without Limits or Throttling). The line $this->sseBuffer .= $chunk; in processSSEStream() has no upper-bound check, and the drain loop below it only executes when strpos($this->sseBuffer, "\n\n") returns a match.

A server that never sends \n\n keeps the strpos from ever matching, so every 4 KiB poll tick grows the buffer by another 4 KiB until process memory is exhausted.

The fix in v0.7.1 adds a guard before the append: if (strlen($this->sseBuffer) + strlen($chunk) > self::MAX_SSE_BUFFER_BYTES) it clears the buffer, nulls the active stream, logs a warning, and returns early. This caps worst-case resident memory to MAX_SSE_BUFFER_BYTES (8 MiB by default) regardless of how much delimiter-free data the server pushes.

The fix

Upgrade mcp/sdk to version 0.7.1 or later. The patch adds a constant MAX_SSE_BUFFER_BYTES (8 MiB) and checks strlen($this->sseBuffer) + strlen($chunk) against it before every append in processSSEStream(). If the limit is exceeded, the stream is aborted and a warning is logged.

No API changes are required.

Reported by tonghuaroot.

References: [1][2][3][4]

Related research