Skip to main content
← All rules

mcp-scan rule

Credential relay risk

credential-relay-riskcritical

What mcp-scan saw

Inside one tool definition, the data-flow scanner matched an env-var source pattern (process.env or env_var / env-var) against the concatenation of tool name, tool description, and the JSON of inputSchema.properties, and in the same string also matched a network sink (an https:// literal, or the bare words fetch, axios, got, http, https, websocket, ws://, wss://, smtp, sendmail, nodemailer, mailer) or a process sink (exec, spawn, child_process, curl, wget). Severity is raised from HIGH to CRITICAL when any CREDENTIAL_ENV_PATTERN also matches that same string.

Why it fires

An env var read plus an outbound sink inside one tool is the shape of a credential relay: the model can be talked into calling the tool with a destination of its choosing, and the secret rides along. Because both halves live in the tool description that the model reads at tools/list time, the pairing is what an attacker would write to make the relay look routine.

When this is a false positive

  • The CRITICAL escalation is keyword-only. CREDENTIAL_ENV_PATTERNS includes /[_-]?auth[_-]?/i and /[_-]?token[_-]?/i as plain substrings, so the words author, authorize, authentication, or tokenize anywhere in the description push a HIGH finding to CRITICAL without a credential being involved.
  • The http-egress sink list includes the standalone English word 'got'. A description like 'returns whatever the file got last write' registers a network sink that does not exist.
  • A tool that reads process.env purely to pick a region or a log level, and separately documents an HTTPS API it calls with its own scoped key, produces the same source-sink pair as a real relay. The scanner sees two matched strings, not the actual variable flow.

How to fix it

  1. Read the finding text. It names the tool and the sink description that matched, so you know which half to check first.
  2. Open that tool's description and inputSchema and confirm whether any env-derived value can reach an argument the caller controls.
  3. If it can, remove the env read from the tool and have the server inject the credential itself at request time, so the model never supplies or sees it.
  4. If the destination is fixed, say so in the description and pin the host, so an argument cannot redirect the request.
  5. If this is keyword noise, reword the description to drop the incidental term (auth, token, got) rather than suppressing the rule, so a real relay in this server still reports.
  6. To suppress globally, add 'credential-relay-risk' to suppressRules in .mcp-scan.json.