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

OAuth state parameter missing or set before consent

HIGHCWE: CWE-352Rule: MCP-262

OAuth state value is generated with a non-cryptographic RNG, OR the state cookie is set in the GET `/authorize` handler before the user has approved consent — both defeat the CSRF protection that state is supposed to provide.

What it is

The OAuth `state` parameter prevents authorization-code interception and CSRF on the callback. It must be (1) generated with a CSPRNG so the attacker can't predict it, (2) stored server-side ONLY after consent has been explicitly approved (so an attacker can't bypass the consent screen by crafting a request that the IdP auto-approves), and (3) validated exactly at the callback. Setting the state cookie before consent renders the consent screen ineffective.

Why it matters for MCP

MCP-proxy OAuth flows are easy to get wrong because they involve THREE state-bearing actors: the MCP client, the MCP proxy server, and the upstream IdP. The state parameter exists between the proxy and the IdP. If the proxy issues state before consent, an attacker can replay the authorize URL and the state cookie + consent cookie auto-approve.

Vulnerable example

example.py
1
import random
2
3
@app.get("/authorize")
4
def authorize(redirect_uri: str) -> RedirectResponse:
5
    state = random.randint(1, 10**9)  # Predictable.
6
    return RedirectResponse(
7
        f"https://accounts.google.com/o/oauth2/v2/auth?state={state}"
8
    )

Secure example

example.py
1
import secrets
2
3
@app.get("/authorize")
4
def authorize(client_id: str, redirect_uri: str) -> HTMLResponse:
5
    return HTMLResponse("<form action='/consent' method='POST'>Approve?</form>")
6
7
@app.post("/consent")
8
def consent(response: Response, redirect_uri: str) -> RedirectResponse:
9
    state = secrets.token_urlsafe(32)
10
    response.set_cookie("__Host-state", state, secure=True, httponly=True, samesite="lax")
11
    return RedirectResponse(
12
        f"https://accounts.google.com/o/oauth2/v2/auth?state={state}"
13
    )

How MCPSafe detects this

Two sub-rules. (1) Weak RNG: `state` / `oauth_state` / `auth_state` / `csrf_state` (+ camelCase) assigned from `random.*` / `Math.random` / `time.time` / `uuid.uuid1` / `Date.now`. Generic `state` only fires when the file has OAuth-proxy context. (2) Cookie-before-consent: file has GET `/authorize` AND a state cookie/session sink, AND no POST `/consent` route exists in the same file.

See the full threat catalog for every documented detection.

Further reading

  • MCP Security Best Practices — OAuth State
  • RFC 6749 §10.12 — CSRF & state

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