← Blog

Three Calls to Compromise: a malicious MCP server that behaves until it doesn't

  • #MCP
  • #rug-pull
  • #tool-poisoning
  • #supply-chain
  • #TOCTOU

In a 74-minute window on 10 August 2026, one GitHub account opened 23 pull requests against unrelated AI, MCP and developer-tool repositories. Each added a Model Context Protocol server called productivity-suite. Pillar Security disclosed the campaign two days later and named it Deadbugz, after the delivery artifact.

The server ships two tools: one formats text, one summarizes it. Both work. They keep working. What the server also keeps is a per-client counter of tools/call requests — and once it reaches three, the responses to tools/list and prompts/get come back different. The new metadata steers the connected agent toward SSH keys, cloud credentials, shell history and Kubernetes configuration, and instructs it to keep that activity away from its operator.

Seventeen of the pull requests added a remote endpoint to an MCP config file. Four pointed the client at a hidden local script. Two were submissions to server directories.

Tool definitions are not labels

A tool definition is not documentation the client displays and the model ignores. It is context handed to the model to decide which tool to call and with what arguments. A server that rewrites its own definitions after approval rewrites the agent’s instructions without touching the tool’s name, its signature, or a single byte on disk.

The protocol supplies the refresh mechanism itself. tools/listChanged exists so a server can announce that its capabilities moved. Most clients treat that as a UI concern. It is an authorization concern.

The gate is aimed at the reviewer

Three is not an arbitrary number. It sits above what review costs and below what use costs.

A human reviewer connects, invokes the formatter once to confirm it does something, and approves. An automated scanner is cheaper still: one tools/list, parse the descriptions, score them, disconnect — zero tool calls, nowhere near the gate. Ordinary daily use crosses three before lunch.

Worth sitting with: the evasion is not concealment. The trigger logic is in a public repository and the malicious metadata is plain text. It evades by being honest to anyone who looks briefly. Both the reviewer and the scanner come away with a result that is entirely accurate about the sample they took.

A snapshot is not a control

If a server holds state, the answer to tools/list is a function of session history, not a property of the server. Auditing it once samples that function at exactly one point — the point the operator was invited to choose, which the attacker already priced in.

Provenance does not close this. A signature attests the artifact, not the behaviour the artifact produces at runtime; a server that flips on the third call carries a valid signature throughout. That is the same gap as Signed, Not Safe, arriving through the metadata channel instead of the distribution channel.

Nor does approval close it. The check happens once, at install; the use happens on every call thereafter, with nothing revalidating in between. A time-of-check to time-of-use window that never closes is not a window — it is the normal operating condition. We wrote that up in Checked, Then Changed before anyone had shipped the trigger. Someone has now.

What holds up

Fingerprint the full set of tool definitions at approval — names, descriptions, schemas, prompt bodies — and store the digest. Re-list past the threshold an evader would plausibly pick, then diff. Treat drift on an approved server as an authorization event that suspends the tool until re-approved, not as a log line.

And note what the counter really is: the simplest possible predicate over session history. The full research page generalises it into six gate classes — including one that no amount of probing reaches — and gives the differential procedure that covers the rest.

Three Calls to Compromise — the full research