Limits
What we catch, what we miss, and what we refuse to claim
What LethalPath checks, what it does not, and which claims it does not make. Each limit below is specific enough to test.
The problem, stated precisely
An agent holds three capabilities at once: it can read text an attacker controls, it can read private data, and it can write somewhere public. Because every tool's output lands in the same context window, an instruction hidden in the first can steer the second into the third. Simon Willison named this the lethal trifecta.
The combination is what matters. Each tool behaves exactly as documented; the bug is in the arrangement. That is why this is a graph problem — untrusted → private → public is a path, and paths are what graphs answer.
What we miss
Labels are inferred, not declared
Of the servers we catalogued, essentially none declare their own trust level. We infer from tool names, with one conservative fallback for anything the rules cannot classify, and we score that inference against a hand-labelled set. Labelling misses are our weakest area; Benchmarks states exactly what we measure — and what we do not.
Annotations are untrusted by design
The MCP specification says clients must treat tool annotations as untrusted unless they come from trusted servers. We follow it. A server that lies about being read-only gets no benefit of the doubt from us — but it also means we cannot simply trust what we are told.
We find paths, not payloads
LethalPath reports that a route exists and which capabilities line it. It does not generate an attack and does not attempt exploitation. A path being absent is not proof of safety; it is proof that we found no route with the labels we have.
Static analysis has a ceiling
We reason about what tools can do, not what a particular model will do with them. The runtime guard demo shows how to close part of that gap by tracking what entered the context; today it answers with simulated tool results and sees only the calls routed through it.
Your data
What leaves your machine
- Nothing. The CLI, the labeler and FalkorDB all run locally; this site shows committed runs and never receives a config
- When you scan a real mcp.json: env values, headers and token-shaped server names are redacted before anything reaches the graph — the CLI prints the redaction count as a receipt
What is never stored
- Your secrets, tokens or keys
- File contents, message bodies or query results
- Your data of any kind — the graph holds capability labels only
The runtime guard records that a call happened, which tool made it, and whether its output was untrusted — by hash, not by content. There is no payload in the graph to leak.
Hard limits, stated plainly
- Not a substitute for scoped tokens. Revoking a grant in the agent does not shrink what the token itself can do. Scope the token too.
- Not a scanner for server vulnerabilities. CVEs, missing OAuth and shell-exec exposure are real problems covered by other tools (credited on Benchmarks). We cover combinations.
- Not exhaustive. We bundle label expansions for 11 conventional servers; a tool the rules cannot classify gets one semi-trusted fallback and is named as unlabelled, so nothing is silently treated as harmless.
- Not a guarantee. A combination we rate safe can become unsafe when a tool changes. Re-scan after upgrades.
- Pinned to falkordb/falkordb:6.0.2 by tag, never latest.
- Every read runs on GRAPH.RO_QUERY, which the server enforces: writes are rejected outright, plus a write-keyword guard, a 3 s timeout and a LIMIT cap.
- algo.maxFlow on 6.0.2 accepts exactly one relationship type, so capacity edges are modelled as a single FLOWS type.
- Shortest-path indexes are not available on 6.0.2. We do not use or claim them.
- The model never receives database credentials and never executes Cypher. Cypher decides; the model only suggests.