Interaction & Data Flow
The MCP server has more privilege than the end-user, and an attacker persuades the server to use that privilege on their behalf.
A confused deputy is a process that holds authority it is supposed to exercise only on instructions from a trusted caller, but that cannot tell which caller is which. When the server acts with its own high privileges instead of the user's limited privileges, any bug becomes a privilege-escalation bug.
MCP servers typically run with a service-account token that has broad access, then pretend to be acting "as the user." They almost never have a way to downscope: the token for the GitHub API can read every repo, and the only thing standing between the model and that access is the tool's own guardrails.
GITHUB_TOKEN = os.environ["GH_TOKEN"] # org-wide admin token |
@server.tool() |
def list_my_repos() -> list[str]: |
# "my" means the token's, not the user's |
r = gh.get("/user/repos", token=GITHUB_TOKEN) |
return [repo["full_name"] for repo in r] |
@server.tool() |
def list_my_repos(ctx: Context) -> list[str]: |
user_token = ctx.principal.github_token # per-user OAuth token |
r = gh.get("/user/repos", token=user_token) |
return [repo["full_name"] for repo in r] |
We flag tool handlers that read tokens or credentials from module-level globals or environment variables rather than from the request context. This is a signal, not proof — some servers legitimately need service-account access — so we tag it as evidence to review.
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