CVE-2026-67431: MCP Ruby SDK: Ruby SSE Session Poisoning
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 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 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
- Python 3.8+
- Install dependencies:
requests
Running the Demo
- 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
- Terminal 2: Start Victim Client
python3 legitimate_client.py
- Terminal 3 - Attacker Client:
Copy the session ID from Terminal 1 and run:
python3 attacker_client.py abc-123-def-456- 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 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
