Token Mismanagement & Secret Exposure (MCP01)

MCP012 checks

Nobody exfiltrates a token from a hardened vault. They read it out of an error message that was trying to be helpful, in a response nobody thought of as output.

Paste a server URL for a free, unauthenticated scan — 27+ checks, graded report, no sign-up.

The free scan only sees what an anonymous caller sees. Authenticated and agentic depth lives in the dashboard scanner.

How the attack works

MCP servers sit between a model and an upstream API, holding credentials for the latter. The leak paths are mundane. An upstream call fails and the server passes the raw exception through, complete with the request object and its Authorization header. A tool returns the full upstream response body for debugging convenience and it includes a session token. A framework announces its name and version in a response header, which is not a secret but is a precise vulnerability shopping list. What makes MCP different is the destination: every one of those leaks lands in a model's context window. From there it is copied into transcripts, logs, analytics, screenshots and — if any other tool in the session has been poisoned or subverted — straight back out to an attacker. A leaked secret in a context window has a much larger blast radius than one in a server log.

An error body that answers more than it was asked

{
  "jsonrpc": "2.0", "id": 4,
  "error": {
    "code": -32603,
    "message": "Request failed: connect ETIMEDOUT 10.0.4.17:5432",
    "data": {
      "stack": "at Pool.query (/srv/app/node_modules/pg/lib/pool.js:412)\n at queryDb (/srv/app/src/db.ts:88)",
      "request": {
        "url": "https://api.internal.corp/v2/records",
        "headers": { "authorization": "Bearer sk_live_9f2a...c41d" }
      }
    }
  }
}

Internal hostname, private IP, dependency versions, source paths and a live bearer token — all now resident in the model's context.

What the scanner checks

Two passive checks. The error verbosity probe sends malformed and failing requests and inspects what comes back for stack traces, filesystem paths, internal hostnames, and credential-shaped strings. The header check looks for framework and version disclosure in response headers, which is the low-severity reconnaissance signal that tells an attacker which CVEs to try.

  • Error Verbosity
  • Server / Framework Version Exposure

What this cannot tell you

These see only what an unauthenticated caller can provoke. Secrets committed to your repository, sitting in environment variables, written into logs, or returned only on authenticated code paths are entirely invisible to an external scan. Nor can a scan judge token lifetime or rotation policy — a server that handles a perfectly-formed request with a ten-year non-rotating key looks identical from outside to one using a five-minute token.

How to fix it

Map errors at the boundary

Catch every upstream failure and return a fixed, enumerated error shape with a correlation id. Log the detail server-side against that id. The person debugging gets everything; the model context gets a code and an id.

Treat the context window as a published surface

Before returning an upstream response, decide field by field what the model actually needs. Pass-through convenience is how tokens, internal ids and other users' data end up in a transcript — assume anything you return may be logged and retained indefinitely.

Scope credentials to the tool, not the server

One powerful key shared by every tool means any single leak is total. Separate credentials per capability, with the narrowest upstream scope each tool needs, so a disclosure costs you one integration rather than the whole surface.

Make rotation routine rather than an incident response

Short-lived tokens fetched at call time are the structural fix, since a leaked credential expires on its own. If you must hold long-lived keys, rotate on a schedule you actually run — the point is that a leak you have not noticed still stops being useful.

Go deeper

Background reading on this risk: From Shodan to Shell: How Attackers Find and Exploit Exposed MCP Servers.

This category is reported as verified evidence and needs at least authenticated scan depth to produce a real result.

Related risks

Frequently asked questions

Why does a framework version header matter?

On its own it is not an exploit — it is targeting information. It converts a broad scan into a specific one by telling an attacker exactly which known vulnerabilities are worth attempting against you. It is rated low for that reason, and it is genuinely trivial to remove.

Is it safe to return upstream errors to help users debug?

Return a stable code and a correlation id, never the raw exception. Upstream error objects routinely embed the request that produced them, and that request carried your credentials.

Where does a leaked token actually end up?

In the model's context first, then anywhere that context goes: the client transcript, provider-side logs, your own observability pipeline, a support screenshot. Treat a token that reached a context window as compromised and rotate it.

Can the scanner find secrets in my source code?

No. It is a black-box scanner that only sees your server's responses. Repository secret scanning is a separate discipline and you want both — they fail in completely different places.

MCP Secret Exposure: How Tokens Leak Out of a Working Server