Like a building pass that works in every office on the block because nobody checks the company name on the badge
CVE-2026-116684 is a resource-confusion flaw in the Model Context Protocol (MCP) Python SDK (mcp on PyPI), affecting all versions from 1.10.0 through 1.29.x and 2.0.0 through 2.1.x. When a server configures bearer-token authentication via AuthSettings with a custom TokenVerifier, the SDK's middleware verifies the token's signature, expiration, and scopes — but never checks the aud (audience) claim against the server's own resource_server_url. If the authorization server (e.g., Azure AD, Okta, Auth0) issues tokens for multiple resource servers, a token legitimately obtained for *Service A* can authenticate to *MCP Server B* on the same IdP. The attacker exercises whatever permissions that token's scopes grant on the wrong server. Fixed in 1.30.0 and 2.2.0, but the fix is opt-in: you must set validate_token_resource=True in AuthSettings and return the token's audience from verify_token() as AccessToken.resource. The SDK plans to make this the default in 3.0.
The vendor scored this MEDIUM / 6.8 with CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N, and that baseline is honest — maybe even slightly generous. The AC:H rating correctly reflects that the attack requires a multi-tenant authorization server *and* a verifier that skips the audience check. But the CVSS can't capture the fact that the overwhelming majority of MCP SDK deployments today use stdio transport (local process pipes), which bypasses HTTP auth entirely and is completely unaffected. Among the subset using Streamable HTTP, many don't configure bearer auth at all, and of those that do, a well-written verify_token() already checks aud as standard JWT hygiene. The population actually vulnerable is a narrow slice of a rapidly growing but still maturing ecosystem.
4 steps from start to impact.
Obtain a valid bearer token from the shared IdP
- Attacker has valid credentials for any service on the same IdP
- Authorization server issues tokens for multiple resource servers
- Attacker must already be an authenticated user somewhere in the org's IdP
- Single-resource IdP configurations are not exploitable
Identify the target MCP server endpoint
https://mcp.internal.corp/mcp). In enterprise deployments, MCP servers are typically on internal networks behind reverse proxies. The attacker needs network reachability to the endpoint.- Target MCP server uses HTTP transport (not stdio)
- Target server is network-reachable to the attacker
- Most MCP servers in 2026 still use stdio transport and are not exposed over HTTP
- Internal MCP servers behind VPN/ZTNA reduce the reachable population
- No Shodan/Censys/FOFA signature for MCP Streamable HTTP endpoints exists yet
Present the cross-resource token to the MCP server
Authorization: Bearer <token>. The server's TokenVerifier.verify_token() validates the signature and expiration but does not check that the aud claim matches this server's resource_server_url. The SDK accepts the token and grants the attacker a valid MCP session with whatever scopes the token carries.- Server uses AuthSettings with token_verifier
- TokenVerifier does NOT independently check the aud claim
- The token's scopes overlap with the server's required_scopes (or no required_scopes are set)
- Security-conscious verifiers already check aud — this is standard JWT validation hygiene
- Scopes may not overlap: a token issued for a file-storage service likely carries scopes irrelevant to the MCP server
- If required_scopes is set and the cross-resource token lacks those scopes, the request is rejected
aud mismatch in JWT claims would catch this if token logging is enabled.Execute MCP tools with the stolen session
- The MCP server exposes tools with meaningful impact
- No additional per-tool authorization checks exist beyond the SDK's bearer gate
- Well-designed MCP servers implement per-tool authorization that checks the caller's identity, not just token validity
- Many MCP servers expose read-only or low-privilege tools (search, summarize) rather than write operations
aud in your custom verify_token() implementation immediately — The fastest mitigation: add an audience check to your existing TokenVerifier.verify_token() method. If token.aud does not contain your server's expected audience identifier, return None (reject). This is the workaround the advisory recommends for unpatched versions and costs zero downtime. No noisgate mitigation SLA applies to MEDIUM — go straight to the 365-day remediation window, but this is a one-line code change worth doing now.mcp>=1.30.0 or mcp>=2.2.0 AND set validate_token_resource=True — The vendor fix. Upgrade the package AND enable the new setting in AuthSettings. Without validate_token_resource=True, the upgrade alone changes nothing. Set resource_server_url to the exact URL clients use to reach your server. Verify verify_token() returns the token's audience as AccessToken.resource. Deploy within the 365-day noisgate remediation SLA.required_scopes on all MCP servers — Configure AuthSettings.required_scopes with scopes specific to your MCP server (e.g., mcp:tools:invoke). Cross-resource tokens will carry scopes for their intended service, not yours, and will be rejected at the scope check even if the audience check is missing. This is defense-in-depth, not a full fix — if the IdP issues broad scopes, it won't help.- Upgrading the SDK without enabling
validate_token_resource=True— the setting defaults toFalsein 1.30.0 and 2.2.0. You will get a deprecation warning but the audience check remains off. Many teams will miss this. - WAF rules — the bearer token is a valid, properly signed JWT. No WAF signature can distinguish a cross-resource token from a legitimate one without understanding the application's expected audience.
- Rotating the IdP's signing keys — the attacker's token is legitimately signed. Key rotation has no effect on this flaw.
- Switching to API key authentication — the MCP SDK's bearer auth is the affected component, but API keys are not a supported auth mechanism in the MCP protocol specification. You'd be breaking protocol compliance.
The supporting signals.
| In-the-wild exploitation | None observed. Not listed in CISA KEV. No reports of active exploitation from any threat intelligence source. |
|---|---|
| Proof of concept | None public. No PoC found on GitHub, pocindex.io, ExploitDB, or Nuclei templates. The attack is conceptually simple (present a valid JWT from another resource) but requires a specific multi-tenant IdP setup to demonstrate. |
| EPSS | 0.365% (30th percentile) — low probability of exploitation in the next 30 days. |
| CVSS vector | CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N — Network-accessible but high attack complexity (requires multi-resource IdP + missing aud check). Low privileges required (valid token for *any* service). High confidentiality and integrity impact on success. No availability impact. |
| Affected versions | mcp (PyPI): ≥ 1.10.0, < 1.30.0 and ≥ 2.0.0, < 2.2.0. Versions before 1.10.0 did not have bearer auth support and are not affected. |
| Fixed versions | 1.30.0 and 2.2.0. Note: upgrading alone is insufficient — you must also set validate_token_resource=True in AuthSettings and return the audience from verify_token(). The setting defaults to True in the upcoming 3.0 release. |
| Scanning / exposure | No Shodan, Censys, GreyNoise, or FOFA dork exists for MCP Streamable HTTP endpoints. A DreamFactory report claims 38.7% of MCP servers run without authentication, meaning those are not affected by this auth-bypass (they have no auth to bypass). The exploitable surface is the subset using HTTP transport + bearer auth + multi-tenant IdP + no aud check. |
| Disclosure timeline | Reported by u-ktdi; advisory published by maxisbey on 2026-10-05. GitHub Advisory Database entry added 2026-10-27. Patches released same day. |
| Reporter | u-ktdi (GitHub handle), published by maxisbey. |
| Related advisories | CVE-2026-111771 is the same flaw in the TypeScript/Node MCP SDK (@modelcontextprotocol/sdk), fixed in 1.32.0 / 2.3.0. CVE-2026-116685 and CVE-2026-116686 are separate MCP Python SDK flaws disclosed in the same batch (cross-origin header forwarding and $ref URL fetching respectively). |
Sources.
- GitHub Security Advisory GHSA-w4fh-qvv9-3v23
- MCP Python SDK Authorization Documentation
- Fix PR #3447 — validate_token_resource setting
- GitLab Advisory Database — CVE-2026-116684
- MCP Python SDK Release v1.30.0
- MCP Python SDK Release v2.2.0
- MCP OAuth 2.1 Resource Server Field Guide — NiteAgent
- OpenCVE — CVE-2026-116684
Why this verdict
- Transport filter eliminates most installs: The flaw only affects MCP servers using Streamable HTTP transport with bearer auth. Industry surveys and vendor data indicate the majority of MCP deployments in 2026 still use
stdio(local) transport, which is completely unaffected. This alone removes a large fraction of the installed base from the vulnerable population. - Prior authentication required: The attacker must already hold a valid token from the same authorization server — they are an authenticated user somewhere in the organization. This is not an unauthenticated remote attack; it is a lateral privilege escalation from one authenticated service to another, which compounds with the next friction point.
- Audience check is standard JWT hygiene: Any
verify_token()implementation that follows OAuth 2.0 / RFC 9068 best practices already checksaud. The SDK's omission is a defense-in-depth gap, not a novel bypass of a control nobody implements. Well-maintained deployments are already protected. - Scope mismatch acts as a natural brake: Even when the token is accepted, it carries scopes issued for the original resource server. If the MCP server enforces
required_scopes, a cross-resource token with unrelated scopes (e.g.,files.readvs.mcp.tools.invoke) will still be rejected. - Role multiplier — CI/CD and internal tool servers: MCP servers deployed as AI-agent tool brokers for CI/CD pipelines or internal databases *could* expose high-value actions (code execution, secret retrieval, DB writes). If the audience-confusion chain succeeds against such a server, the blast radius extends to supply-chain or data-tier impact. However, the chain requires the intersection of HTTP transport + bearer auth + multi-tenant IdP + missing aud check + high-value tools — this intersection is estimated at well under 1% of the current MCP Python SDK installed base, falling below the HIGH floor threshold.
- Role multiplier — general application tier: Most MCP servers expose read-only or low-privilege tools (search, summarize, retrieve context). On these servers, the chain's outcome is limited information disclosure, not fleet-scale compromise. This is the typical deployment and aligns with MEDIUM.
- No exploitation evidence or PoC: EPSS is 0.365%, not KEV-listed, no public PoC, no observed campaigns. The theoretical-only status warrants no upward pressure.
Why not higher?
The HIGH floor requires that a high-value-role outcome represents ≥1% of the installed base. The exploitable population is narrowed by four compounding filters (HTTP transport, bearer auth enabled, multi-tenant IdP, missing aud check), each of which eliminates a significant fraction. While a CI/CD or database-proxy MCP server *could* yield supply-chain impact, the probability that all four conditions AND a high-value tool surface converge is too low to cross the 1% installed-base threshold. The AC:H rating in the CVSS vector already reflects the conditional nature of the attack.
Why not lower?
The flaw is real and the impact on a vulnerable server is C:H/I:H — full read/write access to whatever the MCP server exposes. The attack is network-reachable (not local-only), requires no user interaction, and the fix is opt-in even after upgrading, meaning many teams will upgrade to 1.30.0/2.2.0 and remain vulnerable without the configuration change. The growing adoption of MCP in enterprise settings means the exposed surface will increase over time. LOW would understate the impact for the minority of deployments that *are* exposed.
Crowdsourced verification payload.
Run on any host where the mcp package is installed. Execute with python3 -I check_cve_2026_116684.py. No special privileges required. The script checks the installed version of the mcp package against known-affected ranges.
#!/usr/bin/env python3
"""Check for CVE-2026-116684: MCP Python SDK audience confusion in bearer auth.
Affected: mcp >= 1.10.0, < 1.30.0 AND mcp >= 2.0.0, < 2.2.0
Fixed: 1.30.0, 2.2.0
Exit codes: 1 = VULNERABLE, 0 = PATCHED, 2 = UNKNOWN
"""
import sys
try:
from importlib.metadata import version as get_version, PackageNotFoundError
except ImportError:
from importlib_metadata import version as get_version, PackageNotFoundError
try:
from packaging.version import Version
except ImportError:
print("UNKNOWN — 'packaging' module not installed. Run: pip install packaging")
sys.exit(2)
try:
raw = get_version("mcp")
except PackageNotFoundError:
print("UNKNOWN — 'mcp' package is not installed on this host.")
sys.exit(2)
try:
v = Version(raw)
except Exception:
print(f"UNKNOWN — could not parse version '{raw}'.")
sys.exit(2)
# Affected ranges:
# 1.x: >= 1.10.0, < 1.30.0
# 2.x: >= 2.0.0, < 2.2.0
vulnerable = False
if Version("1.10.0") <= v < Version("1.30.0"):
vulnerable = True
elif Version("2.0.0") <= v < Version("2.2.0"):
vulnerable = True
if vulnerable:
print(f"VULNERABLE — mcp {raw} is in the affected range for CVE-2026-116684.")
print(" Upgrade to >= 1.30.0 (1.x) or >= 2.2.0 (2.x) AND set validate_token_resource=True.")
print(" Immediate workaround: check 'aud' claim in your TokenVerifier.verify_token().")
sys.exit(1)
else:
print(f"PATCHED — mcp {raw} is outside the affected range.")
print(" NOTE: If using 1.30.0+ or 2.2.0+, ensure validate_token_resource=True is set.")
print(" The setting defaults to False; upgrading alone does NOT enable the fix.")
sys.exit(0)