NewApp Monitoring is liveSign up free
AgentbeamBeta
AI agent vulnerability · Other MCP servers and tools

CVE-2026-67431: MCP Ruby SDK: Ruby SSE Session Poisoning

highCVSS 4.0 8.3GHSA-5p9g-j988-pcwv

The MCP Ruby SDK (gem mcp) does not check who owns a session in its Streamable HTTP transport. Anyone holding a valid session ID can POST tool calls into another user's session, and the results go to the victim's SSE stream. Versions up to 0.22.0 are affected. Upgrade to 0.23.0, which adds a session-ownership hook.

Published Jul 30, 2026 · Analysis Oct 25, 2026

What an attacker can do

In lib/mcp/server/transports/streamable_http_transport.rb, the server looks up a session by its Mcp-Session-Id and accepts the request, with nothing tying the ID to the user who created it. The MCP specification says servers should bind session IDs to user-specific information, and the C# and Go SDKs do.

The flow in the report: a victim starts a session and gets an ID such as abc-123. The attacker obtains it, for example from logs or network sniffing (the report treats the theft as out of scope). The attacker POSTs a tool call with that session. The server runs the tool and writes the reply to the victim's open SSE stream, so the victim receives a response they never asked for.

The damage is to integrity: the attacker runs tools the server exposes, under the victim's session context, and the victim has little way to notice. The report also notes disruption through injected responses. It differs from the separate stream replacement bug, where a GET disconnects the victim's stream; here the victim stays connected. The attack needs a leaked session ID.

Who is affected

The mcp gem (modelcontextprotocol/ruby-sdk) up to and including 0.22.0, for servers that use MCP::Server::Transports::StreamableHTTPTransport, the Streamable HTTP and SSE transport. Servers that only use stdio are not exposed to this attack.

bundle list | grep mcp
gem list mcp

mcp

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 mcp

The 0.23.0 release adds a session-ownership hook to StreamableHTTPTransport. The release notes give no usage detail, so read the gem's documentation and wire the hook to your user or auth identity. Upgrading alone may not bind sessions unless you set it up. Meanwhile, avoid logging session IDs, and terminate TLS so IDs cannot be sniffed. The same release adds Host and Origin validation and request size bounds, fixes for other advisories.

How to tell if you were hit

Compare the client address, auth identity or user agent on requests that share one Mcp-Session-Id. A session used from two addresses, or tool calls arriving that the legitimate client never sent, are the sign. On the victim side, look for tool results in the SSE stream with no matching request. Search application and proxy logs for exposed Mcp-Session-Id values, since logs are a way IDs leak.

Original advisory

From the GitHub Advisory Database (CC BY 4.0).

Summary

Vulnerability: Missing Session Ownership Validation in the Ruby MCP SDK's Streamable and SSE HTTP transport implementation. Any attacker with a stolen session ID can execute tools with the victim's session. This is a silent attack - the victim's session is compromised and being used for unauthorized actions, but it is hard to know for the victim

Details

https://github.com/modelcontextprotocol/ruby-sdk/blob/main/lib/mcp/server/transports/streamable_http_transport.rb#L260-L278

Victim starts a legitimate MCP session and receives session ID abc-123 Attacker obtains the session ID (various means - network sniffing, logs, etc. out of scope for this analysis) Attacker sends POST to /messages/abc-123 with a tool call Server accepts the request (no ownership validation!) Server executes the tool and sends response to victim's SSE stream Victim receives attacker's response, thinking it's legitimate

PoC

attacker_client.py legitimate_client.py

Prerequisites

  1. Python 3.8+
  2. Install dependencies: requests

Running the Demo

  1. Terminal 1: Start the Ruby MCP Server

ruby streamable_http_server.rb

Makes use of https://github.com/modelcontextprotocol/ruby-sdk/blob/main/examples/streamable_http_server.rb This server has a tool call notification_tool which the clients call

  1. Terminal 2: Start Victim Client

python3 legitimate_client.py

  1. Terminal 3 - Attacker Client:

Copy the session ID from Terminal 1 and run:

python3 attacker_client.py abc-123-def-456
  1. Back to Terminal 2 - Victim sees the injected response:

Impact

  • Integrity: HIGH - Attacker can execute unauthorized tools and modify state
  • Availability: LOW - Attacker can disrupt victim's session with injected responses

Additional Details

Session Hijacking Protection in MCP Implementations

The MCP specification recommends - "MCP servers SHOULD bind session IDs to user-specific information".

User Binding - Comparison other SDKs

csharp-sdk

  • https://github.com/modelcontextprotocol/csharp-sdk/blob/main/src/ModelContextProtocol.AspNetCore/SseHandler.cs#L36
  • https://github.com/modelcontextprotocol/csharp-sdk/blob/main/src/ModelContextProtocol.AspNetCore/SseHandler.cs#L95-L98

Go-sdk

  • https://github.com/modelcontextprotocol/go-sdk/blob/main/mcp/streamable.go#L66
  • https://github.com/modelcontextprotocol/go-sdk/blob/main/mcp/streamable.go#L316-L320

Comparison with the Stream Replacement vulnerability

Stream Replacement https://github.com/modelcontextprotocol/ruby-sdk/security/advisories/GHSA-qvqr-5cv7-wh35

  • Target: SSE stream connection
  • Attack Vector: GET /mcp
  • What happens to Victim: The connection is disconnected and doesn't receive reponses

Session Poisoning

  • Target: Tool execution
  • Attack Vector: POST /mcp
  • What happens to Victim: The connection stays connected and receives responses of tool calls by attacker

Credited to srikanthramu.

Questions

How does an attacker get the session ID?

The report leaves it out of scope and gives examples like network sniffing and logs. The flaw is that a stolen ID is enough, because the server never checks who sends it.

Is this the same as the Ruby SDK stream replacement bug?

No. The report contrasts them: stream replacement uses a GET to disconnect the victim, while session poisoning uses POST tool calls and leaves the victim connected.

Does upgrading to 0.23.0 fix it by itself?

The release adds a session-ownership hook, but the notes do not say it is on by default. Check the gem docs and configure the hook with your user identity.

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 entries