FlawOpen

flawopen.com/AI Security/mcp-python-sdk-oauth-issuer-confusion

● CWE-345 / CWE-522 · CVSS 7.5[1] · High
FlawOpen Security Research

MCP Python SDK OAuth Issuer-Validation Flaw

Certain Python SDK HTTP OAuth clients could misroute credentials. Upgrade to mcp 1.30.0 or 2.2.0; machine clients must set issuer=.[1], [3], [4]

CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N[1]
💡 Plain English Explainer (ELI5)

A courier should send a sealed key only to the bank named on a directory card the company already trusts. Here, the MCP server could influence the card, and some client paths did not compare its issuer with the expected bank before sending OAuth material. The fix checks the issuer first and binds stored credentials to it.[1], [2]

Core Concepts & Subsystem Terms

MCP HTTP OAuth client
A Python application that connects to an MCP server over HTTP and lets the SDK manage OAuth. This advisory does not affect SDK-built MCP servers, stdio clients, or clients that attach their own tokens or headers.[1]
Authorization-server issuer
The stable identifier for the OAuth service that issues tokens. A client must compare discovered metadata with an issuer it already expects, not accept the value supplied by the server it is connecting to.[1], [2]
Protected Resource Metadata (PRM)
Metadata that can advertise authorization servers for a protected resource. The Python SDK also supports a legacy fallback when PRM is absent.[1], [2]
Machine-to-machine OAuth provider
ClientCredentialsOAuthProvider and PrivateKeyJWTOAuthProvider use pre-provisioned credentials without an interactive sign-in. In affected SDK versions they had no issuer= option to bind those credentials to their intended authorization server.[1], [2]
CWE-345 and CWE-522
The advisory assigns CWE-345 for insufficient verification of data authenticity and CWE-522 for insufficiently protected credentials. The trust-boundary defect is the missing expected-issuer validation.[1], [5], [6]

Framework mappings

  • CWE reference for the weakness described in this guide: CWE-345 CWE-522

Step-by-Step Attack Flow

Step 1

Check whether this client is in scope

The affected setup is a Python mcp HTTP client using an SDK OAuth provider while it holds a stored registration, client secret, signing key, or other provider credential and may connect to an MCP server it does not fully trust. SDK-built servers, stdio clients, and clients attaching their own tokens or headers are not affected.[1]

Step 2

Match the exact release line and discovery path

mcp 1.9.1–1.29.1 lacked issuer validation and credential binding on every discovery path. mcp 2.0.0–2.1.1 was affected when PRM was absent and the legacy fallback ran, or during 403 insufficient_scope step-up discovery. In both release lines, the machine-to-machine providers had no issuer= setting for their pre-provisioned credentials. The advisory package range starts at prerelease 2.0.0a1 and is <2.2.0, so affected 2.0.0 prereleases are also in scope.[1], [3], [4]

Step 3

The connected server influences issuer discovery

A malicious or compromised MCP server can advertise an authorization server in PRM, or omit PRM and supply metadata on a legacy path. Before the fix, affected paths did not consistently compare that metadata's issuer with an independently expected issuer, and stored or pre-provisioned credentials were not consistently bound to their issuer.[1], [2]

Step 4

OAuth material can reach the selected token endpoint

Depending on provider and flow, the client could send a client secret, an authorization code and PKCE verifier, or a signed client assertion to the selected endpoint. The advisory scores unattended providers CVSS 3.1 7.5; its interactive provider is scored 6.5 because a person must start sign-in. The advisory says no CVE has been assigned.[1]

Step 5

Upgrade, pin, and re-register

Upgrade 1.x to mcp 1.30.0 or later, or 2.x to 2.2.0 or later. Set issuer= for ClientCredentialsOAuthProvider and PrivateKeyJWTOAuthProvider; migrate the deprecated 1.x RFC7523OAuthClientProvider. Clear old registrations that lack issuer binding and register again.[1], [2], [3], [4]

MCP Python SDK OAuth exposure and action matrix

Client configurationDocumented affected conditionRequired action
HTTP OAuth client using mcp 1.9.1–1.29.1All discovery paths lacked issuer validation and consistent credential-to-issuer binding.Upgrade to mcp 1.30.0 or later; verify the version actually deployed.
HTTP OAuth client using mcp 2.0.0–2.1.1 stable releasesAffected on the no-PRM legacy fallback and 403 insufficient_scope step-up discovery paths. The advisory package range starts at prerelease 2.0.0a1 and is <2.2.0, so affected 2.0.0 prereleases are also in scope.Upgrade to mcp 2.2.0 or later; verify the version actually deployed.
ClientCredentialsOAuthProvider or PrivateKeyJWTOAuthProvider with pre-provisioned credentialsAffected release lines lacked an issuer= setting to bind these credentials to their expected authorization server.After upgrading, set issuer= explicitly. Migrate the deprecated 1.x RFC7523OAuthClientProvider.
SDK-built server, stdio client, or client-supplied token/headerNot affected by this advisory.No action for this specific flaw; reassess if the application also uses an affected HTTP OAuth client.

Root Cause Analysis

The trust failure had two linked parts: affected discovery paths did not consistently compare the authorization server's metadata issuer with an issuer expected independently of the MCP server, and stored or pre-provisioned credentials were not bound to their intended issuer. A malicious or compromised MCP server could therefore steer a token exchange toward an endpoint it controlled and receive OAuth material held by the client. The fixes derive the expected issuer before discovery, reject mismatches, and bind stored registrations to that issuer; unattended machine-to-machine providers also require an explicit issuer=.[1], [2]

Source Code: Flaw vs. Secure Implementation

This source-grounded configuration comparison shows the new issuer parameter on the fixed machine-to-machine provider. It is available in mcp 1.30.0 and 2.2.0, not in the affected versions, so upgrade before using this setting. The fixed SDK derives an expected issuer before metadata discovery, rejects mismatches, and binds stored registrations to that issuer. Clear registrations created without issuer binding and re-register; if an affected client may have connected to an untrusted MCP server, rotate its client secret and revoke its tokens.[2], [3], [4]

Source excerpt: mcp.client.auth — machine-to-machine provider configuration

UNPATCHED FLAW
import os
from mcp.client.auth import ClientCredentialsOAuthProvider

auth = ClientCredentialsOAuthProvider(
    client_id=os.environ["MCP_CLIENT_ID"],
    client_secret=os.environ["MCP_CLIENT_SECRET"],
)
HARDENED SECURE PATCH
import os
from mcp.client.auth import ClientCredentialsOAuthProvider

auth = ClientCredentialsOAuthProvider(
    client_id=os.environ["MCP_CLIENT_ID"],
    client_secret=os.environ["MCP_CLIENT_SECRET"],
    issuer=os.environ["MCP_AUTH_ISSUER"],
)
# Use mcp >= 1.30.0 (1.x) or >= 2.2.0 (2.x).

Engineering & System Hardening Checklist

Sources

← AI Security Hub Directory →