MCP Ruby SDK 0.23.0 Fixes Five Transport Flaws—but Session Ownership Still Needs Application Logic

Five CVEs published July 30 expose distinct trust-boundary failures in the official Model Context Protocol Ruby SDK: a stolen session identifier can be used to inject tool calls, a browser can reach a local MCP service through DNS rebinding, and three unbounded-input paths can exhaust memory. GitHub’s advisories mark every release of the mcp gem through 0.22.0 as affected and identify 0.23.0 as the first patched version.

The fixed release was published on July 7, before the coordinated advisories became public. It adds secure defaults for most of the batch: loopback-only host validation, origin checks, a 4 MiB request limit, a 30-minute idle-session timeout, a 10,000-session ceiling, and 4 MiB limits for newline-delimited stdio frames. Operators who upgraded earlier may therefore already have the code-level repairs even though the CVE records are new.

One caveat matters more than the version number. The high-severity session-poisoning fix supplies a session_request_validator hook; it cannot infer the authenticated principal for the application. Deployers must connect that hook to their own identity and session store. Without a validator, version 0.23.0 does not enforce user ownership of a session ID.

One stolen identifier can become a victim’s tool call

CVE-2026-67431 covers missing ownership validation in stateful Streamable HTTP sessions. Before 0.23.0, possession of a valid session ID was enough to send a POST on that session. The server would dispatch the JSON-RPC request—including a tools/call—and deliver the response on the victim’s SSE stream. The advisory treats acquisition of the identifier through logs, traffic exposure, or another weakness as a prerequisite; it does not claim that the SDK itself leaks session IDs.

This is different from merely replacing an SSE connection. The victim can stay connected while the attacker exercises tools under the same session, making the unauthorized operation less obvious. The resulting impact depends on what the MCP server exposes: a read-only catalogue limits the damage, while filesystem, deployment, ticketing, or administrative tools can turn session reuse into state changes.

Version 0.23.0 calls the application-provided validator on non-initialization POST, GET, and DELETE requests. A false result returns HTTP 403. The release also records the initialization request’s Origin and rejects a later request carrying a different origin, but the project explicitly describes this as defense in depth rather than authentication: non-browser clients can omit the header. A production validator should resolve the current authenticated user independently and prove that the session belongs to that same principal.

The HTTP transport had no browser-facing boundary

CVE-2026-63118 covers the absence of Host and Origin validation in StreamableHTTPTransport. A malicious website could use DNS rebinding to make its hostname resolve to a service on the victim’s loopback or local network. Because the browser still considered requests same-origin and the Ruby transport did not distinguish the hostile hostname, the page could initialize an MCP session and attempt to list or invoke locally exposed tools.

This is why CORS middleware alone is not a sufficient repair. DNS rebinding preserves the attacker’s origin name while changing the address behind it. The MCP transport specification requires servers to validate Origin on incoming connections and recommends binding local servers to loopback rather than all interfaces.

The new transport enables DNS-rebinding protection by default. It accepts 127.0.0.1, ::1, and localhost, checks same-origin requests, and exposes explicit allowed_hosts and allowed_origins options for non-loopback deployments. Teams widening those lists should use exact deployment names rather than broad patterns, and should preserve equivalent enforcement at a trusted reverse proxy before disabling the SDK check.

Three availability flaws share one missing control: bounds

  • CVE-2026-67432 (high, CVSS 7.5): the Streamable HTTP path read and parsed the complete JSON-RPC POST body before validating a session and imposed no byte ceiling. The advisory’s test sent a 512 MiB request and reports process memory rising from 44 MiB to 1.66 GiB. Version 0.23.0 rejects bodies over 4 MiB by default and limits JSON nesting.
  • CVE-2026-67430 (medium, CVSS 5.3): stateful sessions did not expire by default. An unauthenticated initialization flood could leave session objects resident until clients explicitly deleted them. The repair expires idle sessions after 1,800 seconds and caps concurrent stateful sessions at 10,000 unless the deployer deliberately overrides those defaults.
  • CVE-2026-63119 (medium, CVSS 6.2): server and client stdio transports used Ruby’s IO#gets without a length limit. A peer that continuously emitted bytes without a newline could grow one string until memory exhaustion. Both sides now default to a 4 MiB maximum frame.

The stdio issue has a narrower security boundary than the network flaws. In a conventional launch, the parent process or spawned MCP binary already runs with comparable local authority. It becomes more consequential when an untrusted server is sandboxed but its output is consumed by an unsandboxed host, or when a bridge forwards remotely influenced data into stdio. Either way, the limit protects the host process from a malformed or runaway peer.

The HTTP denial-of-service paths are more directly remote when a Rack-mounted MCP endpoint is reachable without an upstream body limit or authentication. Reverse-proxy limits remain useful even after upgrading because they reject oversized traffic before Ruby allocates a request object, but they should supplement rather than replace the SDK patch.

Defensive engineering guidance

  • Upgrade the mcp gem to 0.23.0 or later. Confirm the resolved version in the lockfile and the version loaded by each running worker; updating a manifest without rebuilding the image is not remediation.
  • Implement session_request_validator. Bind each issued session ID to a stable authenticated principal and tenant. Reject requests when authentication is missing, the principal changes, or the session crosses an organization boundary.
  • Keep session IDs out of observable data. Redact them from access logs, traces, error reports, analytics, browser history, and support bundles. Treat an MCP session identifier as a bearer capability even after adding ownership checks.
  • Retain the new secure defaults. Do not disable DNS-rebinding checks, idle expiry, session caps, request limits, or frame limits merely to restore compatibility. Increase a bound only after measuring a legitimate workload and place a corresponding limit upstream.
  • Authenticate before dispatch. Place Streamable HTTP endpoints behind an identity-aware boundary, authorize each tool independently, and avoid relying on secrecy of a localhost port or session UUID.
  • Constrain the tool blast radius. Run local and remote MCP servers with a task-specific tool allowlist, minimal filesystem and credential access, and restricted network egress. A transport bug should not inherit every capability of the developer account.
  • Monitor lifecycle anomalies. Alert on rapid initialization, repeated HTTP 413 or 503 responses, foreign Host/Origin values, tool calls from a new identity on an existing session, and stdio frame-limit failures.
  • Test the full deployment path. Verify protections through the actual reverse proxy, Rack stack, authentication middleware, and browser-facing hostname. Unit tests that call the transport directly may miss header rewriting or identity loss at an integration boundary.

This advisory batch is a useful map of where an MCP SDK becomes security-critical. The transport is not just a JSON-RPC courier: it decides which browser origins can reach local tools, how long authority survives, whether a session belongs to a user, and how much memory one peer may consume. Version 0.23.0 adds the missing mechanical boundaries, but session ownership remains a policy decision the Ruby application must make explicitly.

Sources: