Configuration & Environment
An MCP server proxying OAuth to a third-party IdP redirects users to the upstream `/authorize` endpoint without first checking per-client consent — an attacker who registers a malicious MCP client can ride the user's existing consent cookie and exfiltrate the auth code.
When an MCP proxy server uses a static client_id with a third-party authorization server and lets MCP clients register dynamically, the third-party IdP's consent cookie is shared across all MCP clients. A malicious client registered with `redirect_uri=attacker.example` can craft an authorize request that the IdP auto-approves (because the consent cookie is still valid from a prior legitimate flow) and redirects the auth code to attacker.example. This is the canonical Confused Deputy attack from the official MCP security best practices.
OAuth-proxy MCP servers are a common pattern ("connect to my Google Drive", "link your GitHub"). The proxy abstracts away IdP details, but introduces this trust-boundary bug because the IdP can't tell which MCP client made the request — only that the proxy did. The defense is the proxy implementing its OWN per-client consent screen, before forwarding to the upstream.
from fastmcp import FastMCP |
from fastapi import FastAPI |
from fastapi.responses import RedirectResponse |
mcp = FastMCP("oauth-proxy") |
app = FastAPI() |
clients = {} |
@app.post("/register") |
def register(redirect_uri: str) -> dict: |
cid = secrets.token_urlsafe(16) |
clients[cid] = {"redirect_uri": redirect_uri} |
return {"client_id": cid} |
@app.get("/authorize") |
def authorize(client_id: str, state: str) -> RedirectResponse: |
# No per-client consent check — forwards immediately. |
return RedirectResponse( |
f"https://accounts.google.com/o/oauth2/v2/auth" |
f"?client_id={GOOGLE_CLIENT_ID}&state={state}" |
) |
@app.get("/authorize") |
def authorize(req, client_id: str, state: str): |
user_id = req.session["user_id"] |
if not consent_required(user_id, client_id): |
return RedirectResponse( |
f"https://accounts.google.com/o/oauth2/v2/auth?client_id={GOOGLE_CLIENT_ID}&state={state}" |
) |
# Render an MCP-owned consent page that names this specific MCP client. |
return HTMLResponse("<form>Allow this client to access your Google Drive?</form>") |
MCPSafe matches files that (1) are MCP server context (`FastMCP` / `McpServer` / `@mcp.tool`); (2) contain a redirect to a known 3P OAuth authorize endpoint (Google `/o/oauth2/auth`, GitHub `/login/oauth/authorize`, Microsoft Entra `/oauth2/v2.0/authorize`, Slack `/oauth/v2/authorize`, Atlassian `/authorize`); (3) wrap that URL in a redirect-shaped sink (`RedirectResponse`, `res.redirect`, Location header); and (4) contain NO per-client consent identifier anywhere (`consent_required`, `check_consent`, `getClientConsent`, `approved_clients`, etc.).
See the full threat catalog for every documented detection.
MCPSafe runs this check — and every other rule in the catalog — on any MCP server you paste in.
Scan now