← Back to Feed CACHED · 2026-10-29 12:56:11 · CACHE_KEY CVE-2026-116684
CVE-2026-116684 · CWE-863 · Disclosed 2026-10-27

MCP Python SDK server bearer-token authentication audience confusion via missing resource validation

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

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.

"Token audience confusion in MCP SDK is real but narrow — most deployments aren't exposed."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Obtain a valid bearer token from the shared IdP

The attacker authenticates to any service registered on the same authorization server (Azure AD tenant, Okta org, Auth0 tenant) that the target MCP server uses. This yields a valid JWT with the attacker's identity and scopes — but issued for a *different* resource server's audience. The attacker needs legitimate credentials for *some* service, making this a post-authentication lateral move.
Conditions required:
  • Attacker has valid credentials for any service on the same IdP
  • Authorization server issues tokens for multiple resource servers
Where this breaks in practice:
  • Attacker must already be an authenticated user somewhere in the org's IdP
  • Single-resource IdP configurations are not exploitable
STEP 02

Identify the target MCP server endpoint

The attacker locates the target MCP server's Streamable HTTP endpoint (e.g., 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.
Conditions required:
  • Target MCP server uses HTTP transport (not stdio)
  • Target server is network-reachable to the attacker
Where this breaks in practice:
  • 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
Detection/coverage: WAF or API gateway logs would show requests to the MCP endpoint from unexpected source IPs.
STEP 03

Present the cross-resource token to the MCP server

The attacker sends a request to the MCP endpoint with 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.
Conditions required:
  • 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)
Where this breaks in practice:
  • 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
Detection/coverage: The SDK logs accepted tokens at DEBUG level; a SIEM rule on aud mismatch in JWT claims would catch this if token logging is enabled.
STEP 04

Execute MCP tools with the stolen session

With a valid session, the attacker can invoke any MCP tools the server exposes — database queries, file operations, code execution, API calls to internal services — limited only by the server's tool definitions and any authorization logic the server implements beyond the SDK's bearer gate. The blast radius depends entirely on what the MCP server proxies.
Conditions required:
  • The MCP server exposes tools with meaningful impact
  • No additional per-tool authorization checks exist beyond the SDK's bearer gate
Where this breaks in practice:
  • 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
Detection/coverage: MCP server application logs should record tool invocations per session. Anomalous tool usage from an unexpected identity is detectable.
03 · Compensating Control

1
MEDIUM 5.5→IGNORE 0.0
SEVERITY REDUCED
Validate 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.
2
MEDIUM 5.5→IGNORE 0.0
SEVERITY REDUCED
Upgrade to 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.
3
MEDIUM 5.5→LOW 3.0
SEVERITY REDUCED
Enforce 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.
4
MEDIUM 5.5→LOW 2.5
SEVERITY REDUCED
Restrict MCP server network exposure with ZTNA / service mesh policies — Ensure MCP Streamable HTTP endpoints are only reachable from authorized AI agent hosts, not the general corporate network. Use a service mesh (Istio, Linkerd) or ZTNA policy to limit the source IPs that can reach port 443/8080 on MCP servers. This doesn't fix the auth flaw but eliminates the network path for most attackers.
What doesn't work
  • Upgrading the SDK without enabling validate_token_resource=True — the setting defaults to False in 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.
04 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNone observed. Not listed in CISA KEV. No reports of active exploitation from any threat intelligence source.
Proof of conceptNone 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.
EPSS0.365% (30th percentile) — low probability of exploitation in the next 30 days.
CVSS vectorCVSS: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 versionsmcp (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 versions1.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 / exposureNo 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 timelineReported by u-ktdi; advisory published by maxisbey on 2026-10-05. GitHub Advisory Database entry added 2026-10-27. Patches released same day.
Reporteru-ktdi (GitHub handle), published by maxisbey.
Related advisoriesCVE-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.

  1. GitHub Security Advisory GHSA-w4fh-qvv9-3v23
  2. MCP Python SDK Authorization Documentation
  3. Fix PR #3447 — validate_token_resource setting
  4. GitLab Advisory Database — CVE-2026-116684
  5. MCP Python SDK Release v1.30.0
  6. MCP Python SDK Release v2.2.0
  7. MCP OAuth 2.1 Resource Server Field Guide — NiteAgent
  8. OpenCVE — CVE-2026-116684
05 · The Call

Final Verdict
= UNCHANGED to MEDIUM (5.5/10)

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 checks aud. 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.read vs. 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.

06 · Verification

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.

noisgate-verify.py
PYTHONREAD-ONLYSAFE
#!/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)