Deploy autonomous AI agents that reason, exploit, and validate complex vulnerability chains — not another scanner, an agentic system that thinks like a senior pentester.
CVE-2026-33946 is a high severity vulnerability with a CVSS score of 8.2. No known exploits currently, and patches are available.
Very low probability of exploitation
EPSS predicts the probability of exploitation in the next 30 days based on real-world threat data, complementing CVSS severity scores with actual risk assessment.
The Ruby SDK's streamable_http_transport.rb implementation contains a session hijacking vulnerability. An attacker who obtains a valid session ID can completely hijack the victim's Server-Sent Events (SSE) stream and intercept all real-time data.
Root Cause The StreamableHTTPTransport implementation stores only one SSE stream object per session ID and lacks:
File: streamable_http_transport.rb - L336-L339:
def store_stream_for_session(session_id, stream)
@mutex.synchronize do
if @sessions[session_id]
@sessions[session_id][:stream] = stream # OVERWRITES existing stream
else
stream.close
end
end
end
Step 1: Legitimate Session Establishment
POST / (initialize) → receives session_id: "abc123"
GET / with Mcp-Session-Id: abc123 → SSE stream connected
Step 2: Session ID Compromise
Step 3: Stream Hijacking
GET / with Mcp-Session-Id: abc123
@sessions["abc123"][:stream] = attacker_stream `# Victim's stream is REPLACED (silently disconnected)
Step 4: Data Interception
The vulnerability happens:
Client 1 connects (GET request)
Please cite this page when referencing data from Strobes VI. Proper attribution helps support our vulnerability intelligence research.
proc do |stream1| # ← Rack server provides stream1 for client 1
@sessions[session_id][:stream] = stream1 # Stored
end
Client 2 connects with SAME session ID (Attack!)
proc do |stream2| # ← Rack provides stream2 for client 2
@sessions[session_id][:stream] = stream2 # REPLACES stream1!
end
Now when the server sends notifications:
@sessions[session_id][:stream].write(data) # Goes to stream2 (attacker!)
# stream1 (victim) receives nothing
Comparison: Python SDK Protection
The Python SDK prevents this vulnerability by rejecting duplicate SSE connections:
Refer: https://github.com/modelcontextprotocol/python-sdk/blob/main/src/mcp/server/streamable_http.py#L680-L685
if GET_STREAM_KEY in self._request_streams: # pragma: no cover
response = self._create_error_response(
"Conflict: Only one SSE stream is allowed per session",
HTTPStatus.CONFLICT,
)
When a duplicate connection attempt is detected, the Python SDK returns an HTTP 409 Conflict error, protecting the existing connection.
Recommended Mitigations For SDK Maintainers
Please find attached two python client files demonstrating the attack
Terminal 1:
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:
python3 legitimate_client_ruby_server.py
What happens:
Terminal 3 (while the legitimate client is running):
python3 attacker_client_ruby_server.py <SESSION_ID>
Replace <SESSION_ID> with the ID from Terminal 2.
What happens immediately:
While the absence of user binding may not pose immediate risks if session IDs are not used to store sensitive data or state, the fundamental purpose of session IDs is to maintain stateful connections. If the SDK or its consumers utilize session IDs for sensitive operations without proper user binding controls, this creates a potential security vulnerability. For example: In the case of the Ruby SDK, the attacker was able to hijack the stream and receive all the tool responses belonging to the victim. The tool responses can be sensitive confidential data.
The MCP specification recommends - "MCP servers SHOULD bind session IDs to user-specific information".
Of the 10 official MCP SDKs, only the following implementations bind session IDs to user-specific information:
attacker_client_ruby_server.py legitimate_client_ruby_server.py The remaining SDKs do not implement session-to-user binding. Most implementations only verify that a session ID exists, without validating ownership. Additionally, SDK documentation does not provide clear guidance on implementing secure session management, leaving security responsibilities unclear for SDK consumers.