Skip to main content
← All rules

mcp-scan rule

Data exfiltration risk

data-exfiltration-riskcritical

What mcp-scan saw

Two emitters. The AST scanner pushes CRITICAL when the command joined with its args matches a pipe into curl, wget, nc, netcat, or socat, or a curl/wget followed by a pipe. The data-flow scanner pushes HIGH when one tool's combined name, description, and inputSchema JSON matches a filesystem, database, or clipboard source and also a network or process sink, unless the same string contains sanitize, anonymize, strip, filter, or mask.

Why it fires

A shell pipe from a local read into a network transfer tool is not a configuration style, it is a transfer. Arguments in an MCP config are passed to execve directly and never shell-expanded, so a pipe character in an argument only does anything if something downstream re-shells it, which is itself the bug. The data-flow half catches the same shape one layer up, where the tool description advertises both a local read and an egress path.

When this is a false positive

  • The AST half matches a literal pipe character anywhere near curl or wget. An argument like --filter=error|warn next to a documented curl wrapper trips it with no data movement.
  • The data-flow half uses /\bfs\b/ as a filesystem source and /\bgot\b/ as an http sink, so a description containing 'fs' and 'got' pairs two sinks that are just English words.
  • A legitimate fetch-and-cache tool (read a URL, write it to disk) matches the same source-sink pair as a read-and-send tool. The scanner has no direction, only membership.
  • A postgres or sqlite MCP server whose description mentions an HTTP admin endpoint pairs a database source with a network sink, even when the two are separate tools in the same server.

How to fix it

  1. Check whether the finding came from the command line or from a tool description. The CRITICAL variant names a pipe and a transfer tool; the HIGH variant names a tool and its source and sink categories.
  2. For the command-line case, remove the pipe from the args array and move the pipeline into a script the server owns, or drop it entirely.
  3. For the tool-description case, split the tool: one tool reads, one tool sends, and the sending tool takes a fixed destination rather than a caller-supplied one.
  4. If the tool really does read and send by design, add an explicit sanitize or mask step and say so in the description. The scanner skips the pairing when the description contains sanitize, anonymize, strip, filter, or mask.
  5. Set allowedDomains in .mcp-scan.json to the hosts this server is supposed to reach, so the AST scanner stops counting them as external.