MCPSafe.io
RegistryThreatsMethodologyDocsPricingScanSign in
MCPSafe.io

Security checks for MCP servers — public packages and private repos, fast or deep.

Legal

Privacy PolicyCookie PolicyTerms of ServiceSecurity disclosure

Resources

State of MCP SecuritySupportSystem statusMade in Germany 🇩🇪

© 2026 MCPSafe. All rights reserved.

GDPR — Privacy Policy
← Threat Catalog

Configuration & Environment

PKCE Method Set to plain

HIGHCWE: CWE-757Rule: MCP-278

MCP servers that initiate OAuth authorization with `code_challenge_method="plain"` violate the MCP Authorization Specification and OAuth 2.1, leaving authorization codes vulnerable to interception by any process that can read the verifier.

What it is

This is a selection-of-less-secure-algorithm vulnerability (CWE-757). PKCE (RFC 7636) defines two transformations between the verifier and the challenge: `S256` (SHA-256 hash) and `plain` (verifier sent as-is). The `plain` method exists only as an OAuth 2.0 legacy escape hatch for clients that cannot perform SHA-256 — it provides no cryptographic protection because the challenge equals the verifier, so any interception (logs, debug proxies, malicious browser extension) recovers the verifier directly and can redeem the authorization code.

Why it matters for MCP

OAuth 2.1 and the MCP Authorization Specification (2025-11-25) make `S256` mandatory for all clients. MCP servers and proxies routinely run on developer machines where verbose request logging, mitmproxy debugging, and shared OS keyrings are common — exactly the environments where `plain` PKCE provides zero defense. Because MCP authorization codes grant access to whatever scopes the user just consented to (file system, cloud accounts, cloud storage), a single intercepted code can be a full takeover of the user's tool surface.

Vulnerable example

example.py
1
from fastmcp import FastMCP
2
3
mcp = FastMCP("my-mcp")
4
5
@mcp.tool()
6
def list_files() -> list[str]:
7
    return []
8
9
def build_auth_url():
10
    # VULNERABLE: `plain` provides no cryptographic protection.
11
    # Any process that sees the verifier can redeem the auth code.
12
    return create_authorization_url(
13
        client_id="my-mcp",
14
        code_challenge_method="plain",
15
        code_challenge="abcdef-verifier-cleartext",
16
    )

Secure example

example.py
1
import hashlib
2
import base64
3
import secrets
4
from fastmcp import FastMCP
5
6
mcp = FastMCP("my-mcp")
7
8
@mcp.tool()
9
def list_files() -> list[str]:
10
    return []
11
12
def build_auth_url():
13
    verifier = secrets.token_urlsafe(96)
14
    challenge = (
15
        base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest())
16
        .rstrip(b"=")
17
        .decode()
18
    )
19
    return create_authorization_url(
20
        client_id="my-mcp",
21
        code_challenge_method="S256",
22
        code_challenge=challenge,
23
    )

How MCPSafe detects this

MCPSafe fires per-occurrence when the literal string `"plain"` is assigned to any case-variant of `code_challenge_method` / `codeChallengeMethod` — via `=` (Python kwarg or JS object literal), `:` (dict literal or JSON), or as a JSON string `"code_challenge_method": "plain"`. Detection is gated to files that import or reference an MCP SDK so unrelated OAuth code in non-MCP repos is not flagged. The v1 detection is intentionally literal-only: if the method is set via a runtime variable (`method = config.get("pkce_alg")`), the rule does not fire — move the policy decision inline so it is auditable.

See the full threat catalog for every documented detection.

Further reading

  • MCP Authorization Specification (2025-11-25)
  • RFC 7636 — Proof Key for Code Exchange
  • OAuth 2.1 Draft
  • CWE-757: Selection of Less-Secure Algorithm During Negotiation

Scan an MCP server for this issue

MCPSafe runs this check — and every other rule in the catalog — on any MCP server you paste in.

Scan now