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

engine facts a reviewer should know
  • 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.