Scan
Example scan: GitHub, files and git
This page shows output from a committed CLI run, not a live scanner. The config is built from the official MCP server READMEs. Nothing is pasted or sent from here; to scan your own config, run the CLI on your machine.
Scan summary
The config that was scanned
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>" }
},
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem",
"/Users/username/Desktop", "/path/to/other/allowed/dir"]
},
"git": {
"command": "uvx",
"args": ["mcp-server-git", "--repository", "path/to/git/repo"]
}
}
}
What the scanner reports
↓ github.get_issue · reads untrusted
↓ agent context · now tainted
private local repo · the project itself
↓ git.diff · reads private
↓ agent context
↓ git.push · writes remote
↓ public sink public repo · origin/main
one of 567 routes — scan prints the 25 shortest by hops; --json carries the same 25 plus the full count.
Breaks 567 of 567 paths · revokes 9 write grants (every github write + git.push) · verified 0 remain when the same query re-runs without them. When cuts tie on cost the engine prefers the write side — the agent keeps its read and loses only the publish.
policy diff
| tool | label | source | conf |
|---|---|---|---|
| github.get_issue | untrusted | rule | 0.70 |
| filesystem.write_file | private | rule | 0.60 |
| git.push | sink | rule | 0.70 |
The queries, verbatim
Cypher executed — leak-path query · read-only · 3 s timeout · LIMIT capped at 100
MATCH (u:Resource {trust:'untrusted'})-[:FLOWS]->(:Tool)-[:FLOWS]->(ctx:Context) MATCH leak = (p:Resource {sensitivity:'private'}) -[:FLOWS*1..5]->(ctx) -[:FLOWS*1..5]->(s:Resource {visibility:'public'}) RETURN u.kind AS injected_from, [n IN nodes(inj) | coalesce(n.kind, n.name, n.id)] AS injection, [n IN nodes(leak) | coalesce(n.kind, n.name, n.id)] AS chain, [n IN nodes(leak) WHERE n:Tool | n.name] AS tools, length(leak) AS hops ORDER BY hops ASC LIMIT 25
Cypher executed — min-cut · one algo.maxFlow call; both cuts derived from it in code
MATCH (p:Resource {sensitivity:'private'}) WHERE p.visibility IS NULL OR p.visibility <> 'public' WITH collect(p) AS src MATCH (s:Resource {visibility:'public'}) WHERE s.sensitivity IS NULL OR s.sensitivity <> 'private' WITH src, collect(s) AS dst CALL algo.maxFlow({ sourceNodes: src, targetNodes: dst, relationshipTypes: ['FLOWS'], capacityProperty: 'capacity' }) YIELD maxFlow, edges, edgeFlows RETURN maxFlow, edges, edgeFlows
One call, one relationship type (all 6.0.2 accepts); it returns flows, not the cut, so the residual graph is walked in mincut.ts to produce the edge set above.