On October 20, 2026, GitHub Advisory Database published CVE-2026-111771 (GHSA-rvq5-wwqv-78pq), detailing an authentication delegation flaw in the official Model Context Protocol (@modelcontextprotocol/sdk) TypeScript implementation.
When running MCP servers over remote HTTP transports (such as Server-Sent Events), the default bearer authentication middleware accepted valid tokens issued for completely different services within the same identity ecosystem.
If an attacker held a legitimate user token for a low-privilege internal dashboard, they could replay that token against a high-privilege MCP server, executing unauthorized tool calls and extracting sensitive prompt context.
Here is the root-cause analysis, reproducible code fixtures, and how to verify your tokens safely.
The Root Cause: Confused Deputy on Audience (aud)
Under RFC 7519 (JSON Web Token), the aud (Audience) claim identifies the intended recipients of the token. Furthermore, RFC 6750 (OAuth 2.0 Bearer Tokens) dictates that resource servers MUST reject tokens not explicitly intended for their service.
In affected versions of @modelcontextprotocol/sdk, the default HTTP transport verified:
- Token format and cryptographic signature integrity using the configured public key or JWKS endpoint.
- Token expiration timestamp (
exp).
However, it omitted strict evaluation of the aud claim. If an organization used a single Identity Provider (such as Auth0, Keycloak, or Okta) across multiple internal microservices, any valid token issued for Service-A was erroneously accepted by MCP-Server-B.
{
"iss": "https://auth.company.internal/",
"sub": "user_dev_8841",
"aud": "https://analytics-dashboard.company.internal",
"exp": 1792576800,
"scope": "read:analytics"
}
Vulnerable MCP servers checked the signature against auth.company.internal, ignored that aud was intended only for analytics-dashboard, and granted agent-level tool access.
Step-by-Step Fix: Before vs After
❌ Vulnerable Implementation
In affected implementations, middleware passed a generic verification callback without specifying audience constraints:
// ❌ VULNERABLE: Lacks audience (aud) and issuer (iss) enforcement
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import jwt from "jsonwebtoken";
const server = new McpServer({ name: "InternalAgent", version: "1.0.0" });
app.get("/sse", (req, res) => {
const authHeader = req.headers.authorization;
const token = authHeader?.split(" ")[1];
try {
// Only verifies cryptographic signature; permits any token issued by JWKS
const decoded = jwt.verify(token, process.env.IDP_PUBLIC_KEY!);
req.user = decoded;
} catch (err) {
return res.status(401).send("Unauthorized");
}
const transport = new SSEServerTransport("/messages", res);
server.connect(transport);
});
✅ Hardened Production Configuration
To mitigate CVE-2026-111771, upgrade @modelcontextprotocol/sdk to the latest release and enforce explicit audience and issuer validation options in your verification chain:
// ✅ HARDENED: Strict audience, issuer, and algorithm validation
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import jwt from "jsonwebtoken";
const MCP_RESOURCE_AUDIENCE = "https://mcp.company.internal/api/v1";
const TRUSTED_ISSUER = "https://auth.company.internal/";
app.get("/sse", (req, res) => {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith("Bearer ")) {
return res.status(401).json({ error: "Missing or malformed bearer token" });
}
const token = authHeader.substring(7);
try {
// Enforces RFC 7519 audience and issuer validation strictly
const decoded = jwt.verify(token, process.env.IDP_PUBLIC_KEY!, {
audience: MCP_RESOURCE_AUDIENCE,
issuer: TRUSTED_ISSUER,
algorithms: ["RS256", "ES256"],
});
req.user = decoded;
} catch (err) {
return res.status(403).json({
error: "Forbidden: Token rejected due to invalid audience or expired credentials"
});
}
const transport = new SSEServerTransport("/messages", res);
server.connect(transport);
});
Inspecting Tokens Locally (Zero Server Upload)
Before deploying security patches, developers should verify their token claims client-side to ensure compliance with intended scopes and target audiences.
Never paste internal production tokens into web tools that send payloads to remote backends. You can inspect your headers and claims 100% locally in your browser memory using the open-source DevOmniTools JWT & JWS Decoder:
- Paste your Bearer token into the decoder.
- Confirm the
audclaim matches your MCP endpoint URI. - Check the
exptimestamp locally without network telemetry.
Transport Layer Comparison
| Transport Layer | Protocol | Vulnerable to CVE-2026-111771? | Required Mitigation |
|---|---|---|---|
| stdio | Local IPC / UNIX Pipes | ❌ No | Process isolation; local CLI arguments only. |
| SSE (HTTP) | Server-Sent Events | ⚠️ Yes (High) | Enforce audience parameter in JWT verify hook. |
| WebSockets | WSS Handshake | ⚠️ Yes (Medium) | Validate token headers during initial upgrade handshake. |
| Custom Gateway | Reverse Proxy / Envoy | ❌ No (If proxy validates) | Validate JWT at Envoy/Ingress using jwt_authn filter with exact audiences. |
Original advisory documentation and multilingual references available on DevOmniTools Research.
Top comments (0)