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

Token passthrough to downstream APIs

HIGHCWE: CWE-345Rule: MCP-265

MCP server forwards an inbound `Authorization` header value (or accepts JWTs without an audience check) directly to a downstream API — the only **MUST NOT** in the official MCP security best practices.

What it is

Token passthrough means the MCP server acts as a transparent proxy for tokens issued to a different audience. Two shapes: (1) a JWT decoded without `audience=` validation — any JWT with a valid signature (regardless of its intended target) is accepted; (2) the inbound `request.headers["Authorization"]` value forwarded as-is into a downstream HTTP call. Both let an attacker replay a token across services and break the audience boundary.

Why it matters for MCP

The MCP spec calls this out as the only MUST NOT in the entire security best practices document because it breaks the entire audience-validation model OAuth depends on. An MCP server should always acquire its OWN token for downstream APIs (via OAuth client-credentials, on-behalf-of, or RFC-8693 token exchange), never reuse the user's upstream token.

Vulnerable example

example.py
1
from fastmcp import FastMCP
2
import jwt
3
import httpx
4
5
mcp = FastMCP("my-mcp")
6
7
@mcp.tool()
8
def whoami(token: str) -> dict:
9
    # No audience= kwarg.
10
    payload = jwt.decode(token, key="my-secret", algorithms=["RS256"])
11
    return {"sub": payload["sub"]}
12
13
@mcp.tool()
14
def proxy_call(request, path: str) -> dict:
15
    # Inbound Authorization forwarded to downstream verbatim.
16
    res = httpx.get(
17
        f"https://downstream.example/{path}",
18
        headers={"Authorization": request.headers["Authorization"]},
19
    )
20
    return res.json()

Secure example

example.py
1
from fastmcp import FastMCP
2
import jwt
3
import httpx
4
import boto3
5
6
mcp = FastMCP("my-mcp")
7
sm = boto3.client("secretsmanager")
8
MY_AUD = "https://api.my-mcp-server.example"
9
10
def get_downstream_credential() -> str:
11
    return sm.get_secret_value(SecretId="downstream/api-key")["SecretString"]
12
13
@mcp.tool()
14
def whoami(token: str) -> dict:
15
    payload = jwt.decode(token, key="my-secret", algorithms=["RS256"], audience=MY_AUD)
16
    return {"sub": payload["sub"]}
17
18
@mcp.tool()
19
def proxy_call(path: str) -> dict:
20
    res = httpx.get(
21
        f"https://downstream.example/{path}",
22
        headers={"Authorization": f"Bearer {get_downstream_credential()}"},
23
    )
24
    return res.json()

How MCPSafe detects this

Two sub-rules, both file-wide and gated to MCP-server context. (1) `jwt-no-audience`: file decodes/verifies JWTs but never references audience verification anywhere (`audience=`, `audience:`, `"aud":`, `verify_aud=True`, `validate_audience()`). (2) `authorization-passthrough`: file forwards inbound `request.headers["Authorization"]` / `req.headers.authorization` into an outbound HTTP call's `Authorization` header in the same expression. Calls routed through `mint_downstream_token` / `exchange_for_downstream` / `get_downstream_credential` are exempt.

See the full threat catalog for every documented detection.

Further reading

  • MCP Security Best Practices — Token Passthrough (the only MUST NOT)
  • RFC 8693 — OAuth Token Exchange

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