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

Interaction & Data Flow

Runtime Secret Exfiltration via MCP Tool Response

HIGHCWE: CWE-532Rule: MCP-202

Unredacted secrets from environment variables, cloud secret stores, or OAuth tokens flow directly into MCP tool responses or logs, exposing credentials to any client or observer of the conversation. Sibling rules in the sensitive-data-exposure (CWE-532) family: MCP-251 (PII to logs) and MCP-306 (auth headers logged before auth check).

What it is

CWE-532 describes sensitive information being written to an externally-observable output channel — logs, HTTP responses, or application output — without masking. In MCP tool handlers, this occurs when values drawn from `process.env` keys matching SECRET/TOKEN/KEY/PASSWORD, AWS Secrets Manager `SecretString`, KMS `Plaintext` bytes, or similarly-named variables are passed directly to response builders or loggers without a redaction step.

Why it matters for MCP

MCP tool responses are consumed verbatim by the LLM and forwarded to the client, meaning a single unredacted secret reaches at minimum two external observers: the model context (which may be logged or fine-tuned upon) and the end user. Unlike a traditional API where a developer controls the response shape, an LLM orchestrator may invoke a tool in an unexpected composition — for example, chaining a 'get config' tool output into a 'send message' tool — causing secrets to propagate across tool boundaries invisibly. The model itself can be prompted to repeat or relay secret values it has seen in prior tool results, dramatically amplifying the blast radius.

Vulnerable example

example.js
1
server.tool("get-db-info", { db: z.string() }, async ({ db }) => {
2
  const password = process.env.DB_PASSWORD;
3
  const host = process.env.DB_HOST;
4
5
  console.log("Connecting to", host, "with password", password);
6
7
  return {
8
    content: [{
9
      type: "text",
10
      text: `Host: ${host}, Password: ${password}`
11
    }]
12
  };
13
});

Secure example

example.js
1
const redact = (val) => val ? val.slice(0, 4).padEnd(val.length, "*") : "[unset]";
2
3
server.tool("get-db-info", { db: z.string() }, async ({ db }) => {
4
  const password = process.env.DB_PASSWORD;
5
  const host = process.env.DB_HOST;
6
7
  console.log("Connecting to", host, "with password", redact(password));
8
9
  return {
10
    content: [{
11
      type: "text",
12
      text: `Host: ${host}, Password: ${redact(password)}`
13
    }]
14
  };
15
});

How MCPSafe detects this

MCPSafe performs taint tracking from secret-named sources — `process.env` accesses whose key matches `SECRET|TOKEN|KEY|PASSWORD|CREDENTIAL|BEARER` (case-insensitive), AWS SDK `getSecretValue().SecretString`, KMS `decrypt().Plaintext`, and local identifiers matching the same pattern — to sinks including MCP `content: [{text: ...}]` response builders, `console.log/warn/error`, `process.stdout.write`, HTTP response `send/json`, and `fs.writeFile`. Flows are flagged when no recognized redaction call (e.g., `mask()`, `redact()`, `obscure()`, or a slice-plus-pad pattern) wraps the tainted value before it reaches the sink; direct passthrough and string-template interpolation of the raw value are both matched.

See the full threat catalog for every documented detection.

Further reading

  • CWE-532: Insertion of Sensitive Information into Log File
  • OWASP: Sensitive Data Exposure
  • AWS Secrets Manager: Best Practices
  • MCP Specification: Tool Result Content

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