mcp-scan rule
Credential relay risk
credential-relay-riskcriticalWhat 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
- Read the finding text. It names the tool and the sink description that matched, so you know which half to check first.
- Open that tool's description and inputSchema and confirm whether any env-derived value can reach an argument the caller controls.
- 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.
- If the destination is fixed, say so in the description and pin the host, so an argument cannot redirect the request.
- 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.
- To suppress globally, add 'credential-relay-risk' to suppressRules in .mcp-scan.json.