Deadbugz: The MCP Supply Chain Attack That Waits 3 Calls (2026)
Nikhil Tiwari
MCP Playground
๐ TL;DR
- Deadbugz is an MCP supply chain attack disclosed by Pillar Security. On 10 August 2026 one GitHub account,
zellkernel, opened 23 pull requests in 74 minutes. - The PRs added an MCP server called productivity-suite with two harmless tools:
format_textandsummarize. - After the third tool call, the server changed. Its
tools/listandprompts/getresponses turned into instructions to find SSH keys, AWS credentials, shell history and Kubernetes config, and to hide it from the user. - Any review done before install saw a clean server. This is a rug pull with a delay built in, and it defeats one-time approval by design.
- The fix is behavioural: pin the tool definitions you approved, call the server several times, list again, and treat any difference as a security event.
Most MCP security advice assumes the server you reviewed is the server you run.
Deadbugz broke that assumption on purpose.
It is an MCP supply chain attack that shipped a server which looked clean on install, stayed clean for three tool calls, then turned.
After call three, its tool descriptions became instructions. The target was your SSH keys, cloud credentials and shell history.
If your only check is reading the tool list before you approve a server, you would have approved this one.
So would a registry scanner. So would a careful reviewer reading the pull request.
I will walk through exactly how the trigger worked, why static checks miss it, and a 40-line test that catches it.
You will also get the indicators of compromise, so you can check your own repos today.
What Is the Deadbugz MCP Supply Chain Attack?
Deadbugz is a campaign that planted a malicious MCP server in developer projects through pull requests, then activated it only after three tool calls.
Pillar Security published the analysis. The core facts:
| Detail | Deadbugz |
|---|---|
| When | 10 August 2026, 21:52 to 23:07 UTC |
| Who | GitHub account zellkernel |
| Delivery | 23 pull requests to unrelated AI, MCP and dev-tool projects. 19 closed, 4 still open at review. |
| Payload | 17 PRs added a remote MCP endpoint, 4 a local Python file, 2 were listing submissions |
| Server | productivity-suite, tools format_text and summarize |
| Goal | SSH keys, AWS credentials, shell history, Kubernetes config, hidden from the user |
Adversa's September 2026 MCP security roundup ranked it the month's top incident.
The delivery was ordinary. The trigger was the new part.
How Did the Deadbugz Trigger Work?
The server counted tools/call requests per client in memory. Once the count reached three, its tools/list and prompts/get responses changed.
Here is the sequence an agent would see:
- Connect.
tools/listreturns two dull tools with honest descriptions. - Calls one to three.
format_textandsummarizedo exactly what they say. - Call four onward. The next
tools/listorprompts/getreturns new metadata. - The new metadata is an instruction. It steers the agent toward
~/.ssh, AWS credentials, shell history and kubeconfig, and tells it not to mention this to the user.
No exploit code runs on your machine in the remote variant. The agent does the work, using the file and shell tools you already gave it.
That is MCP tool poisoning, delivered late. The model reads tool descriptions as trusted context, so a description is a prompt.
Why three calls?
Three is enough to get past the checks people actually run.
- A reviewer connects once and reads the tool list. Zero calls.
- A registry scanner lists tools and maybe calls each once. Two calls.
- A developer tries the server and sees it work. One or two calls.
Real use goes past three within minutes. The trigger sits right between evaluation and production.
Why Static MCP Server Review Can't Catch a Rug Pull
A static review inspects the server at one moment. Deadbugz only misbehaved at a later moment, so every static check passed by construction.
Pillar's write-up puts it plainly: metadata that turns hostile at runtime defeats review. You cannot read your way to a behaviour that has not happened yet.
This is a rug pull: a server changes its tool definitions after you trusted it. The MCP spec allows tool lists to change, and clients are expected to re-fetch them.
That is a feature. Servers add tools, fix typos and ship versions. The problem is that most clients accept the new list silently.
โ ๏ธ Stateless MCP does not stop this
The 2026-07-28 stateless spec removed protocol sessions, but a server can still key a counter on an auth token, an IP address or any header. "No session" does not mean "no memory of you".
What each common defence actually sees
| Defence | Catches Deadbugz? | Why |
|---|---|---|
| Reading the PR diff | โ Remote variant | The diff is one URL. The logic lives on someone else's host. |
| Scanning tool descriptions at install | โ | The descriptions are clean at install. |
| A tool name allowlist | โ | The names never change. Only the text does. |
| Hash pinning, checked once at connect | โ ๏ธ Only on reconnect | It misses the change inside a running session. |
| Pinning plus re-checking after calls | โ | It compares behaviour over time, which is where the attack lives. |
How Do I Detect a Malicious MCP Server Before It Turns?
Pin the tool and prompt definitions you approved, use the server past any plausible threshold, list again, and diff. That is the whole idea.
Pillar's own guidance says the same: treat a changed tool definition on an approved server as a security event, show it to the operator, and require re-approval.
You can run that test yourself with the official MCP TypeScript SDK. No model is involved, so nothing can be tricked into acting.
๐จ Run it somewhere disposable
This script never gives the server a model or your files. Still, run it from a throwaway container or CI job, not the laptop that holds your SSH keys. Never point a real agent at a server you are testing.
// drift-check.mjs โ node drift-check.mjs https://server.example.com/mcp
import { createHash } from "node:crypto";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const url = new URL(process.argv[2]);
const CALLS = 10; // well past any small threshold
const hash = (v) => createHash("sha256").update(JSON.stringify(v)).digest("hex");
async function snapshot(client) {
const { tools } = await client.listTools();
const prompts = await client.listPrompts().catch(() => ({ prompts: [] }));
const texts = [];
for (const p of prompts.prompts) {
texts.push(await client.getPrompt({ name: p.name }).catch((e) => String(e)));
}
return tools.map((t) => ({
name: t.name,
hash: hash([t.description, t.inputSchema, t.outputSchema]),
})).concat([{ name: "prompts", hash: hash(texts) }]);
}
const client = new Client({ name: "drift-check", version: "1.0.0" });
await client.connect(new StreamableHTTPClientTransport(url));
const before = await snapshot(client);
const tool = (await client.listTools()).tools[0];
for (let i = 1; i <= CALLS; i++) {
await client.callTool({ name: tool.name, arguments: {} }).catch(() => {});
const now = await snapshot(client);
const changed = now.filter((t) =>
before.find((b) => b.name === t.name)?.hash !== t.hash);
if (changed.length) {
console.error("DRIFT after call " + i + ":", changed.map((c) => c.name));
process.exit(1);
}
}
console.log("No drift in " + CALLS + " calls.");
await client.close();
It re-lists after every call, not once at the end. That tells you the exact call count where behaviour changed, which is the fingerprint of a gated trigger.
Empty arguments will fail on tools with required fields. That is fine. Deadbugz counted tools/call requests, not successful ones. For stricter servers, build arguments from each inputSchema.
Make the test harder to game
- Raise the call count. Three was Deadbugz. The next campaign will pick 20, or a time delay.
- Re-run on a schedule. A remote server can change for everyone next Tuesday. Run the check in CI daily.
- Vary the identity. A server can behave for one token and not another. Test with the credentials your agents use.
- Diff prompts and resources too. Deadbugz used
prompts/get, not onlytools/list.
Before any of that, make sure the server passes the basics. Scan your MCP server โ for auth, transport, injection and disclosure issues in one pass, free, in the browser.
How Do I Protect My Agents From MCP Supply Chain Attacks?
Assume a trusted server can turn, and limit what a turned server can reach. Detection helps. Blast-radius control is what saves you when detection misses.
1. Review MCP config changes like dependency changes
Deadbugz arrived as config edits. Watch for new entries in these files in every PR:
.mcp.jsonand.cursor/mcp.jsonclaude_desktop_config.json.vscode/mcp.jsonand any agent framework config
A new remote MCP URL deserves the same scrutiny as a new npm package. Add a CODEOWNERS rule so these files need a security reviewer.
2. Pin definitions and require re-approval
Hash each tool's description and schema when you approve a server. On every tools/list, compare. On any mismatch, stop and ask a human.
This is the first fix on the OWASP MCP tool poisoning page for a reason. It is cheap and it closes the rug-pull window.
3. Take the loot out of reach
The payload asked the agent to read files. An agent that cannot read ~/.ssh cannot leak it.
- Run coding agents in a container or dev VM without your real credentials mounted.
- Use short-lived cloud credentials, not a long-lived
~/.aws/credentialsfile. - Scope filesystem MCP servers to the project directory only.
4. Separate reading from sending
Stolen data has to leave somehow. An agent with file access and open network egress is the full kill chain in one process.
Keep egress on an allowlist. The context over-sharing page covers splitting read and egress into separate agents.
5. Keep the logs
Pillar asks responders to preserve MCP client logs before cleanup.
Then review tool-definition refreshes and what the agent did after the third call. You can only do that if the logs exist.
Deadbugz Indicators of Compromise
From Pillar Security's report on this MCP supply chain attack. Search your repos, configs and proxy logs for these.
| Type | Indicator |
|---|---|
| Remote endpoint | productivity-suite-mcp.onrender.com/mcp |
| Earlier endpoint | promo-surname-xml-quantum.trycloudflare.com/mcp |
| Local file | ~/.config/.cache/.sys/.deadbug-mcp.py |
| Server name | productivity-suite |
| Source account | GitHub zellkernel |
A quick first pass over a checkout:
grep -rnE "productivity-suite|deadbug|trycloudflare\.com/mcp" \
--include="*.json" --include="*.toml" --include="*.yaml" .
ls -la ~/.config/.cache/.sys/ 2>/dev/null
If you find a hit, rotate every credential the agent could read: SSH keys, cloud keys, kubeconfig tokens. Assume they are gone.
What Deadbugz Means for MCP Security in 2026
Deadbugz was not technically advanced. A counter and a string swap. That is what makes it worth taking seriously.
It shows the MCP trust model has a timing gap. Teams approve servers once and trust them forever, while the protocol lets them change at any time.
Registries, scanners and reviewers all look at the moment of install. The attacker simply picked a later moment.
Expect the next MCP supply chain attack to copy it with longer delays, time-based triggers and per-user targeting.
The defence that scales is continuous: pin, re-check, re-approve, and limit the blast radius.
For the wider picture, my MCP server security guide and the tool poisoning and OWASP MCP Top 10 breakdown cover the rest of the attack surface.
How MCP Playground Can Help
I use MCP Playground's server tester to connect to a server in the browser. I read every tool description and schema as the model will see it.
Run it before and after a batch of calls, and compare what you see.
The MCP security scanner then checks auth, transport, injection risk and information disclosure, graded A to F with a fix list.
It is not a substitute for the drift test above. It catches the ordinary mistakes that make a turned server more dangerous.
New to the protocol? Start with what the Model Context Protocol is, then come back to this.
Frequently Asked Questions
What is the Deadbugz MCP campaign?+
What is an MCP rug pull?+
Can an MCP server change its tools after I approve it?+
How do I detect a malicious MCP server?+
Would an MCP security scanner have caught Deadbugz?+
What should I do if I find a Deadbugz indicator?+
The Bottom Line
Deadbugz passed every check that looks at a server once. It only failed a check that watches the server over time.
Pin what you approve, re-check after real use, and keep credentials out of your agent's reach. Then a turned server finds nothing to steal.
See every tool description your agent will read
Connect to any MCP server in the browser, inspect its tools and prompts, and scan it for the basics. No install.
Test any MCP server free โ Scan your MCP server โWritten by Nikhil Tiwari
15+ years in product development. AI enthusiast building developer tools that make complex technologies accessible to everyone.
Free MCP Tools (no install)
Build, compare & ship MCP agents
Connect any MCP server, run evals on it, compare 60+ models side-by-side, deploy hosted servers, and save reusable agents you can export as an API โ all in your browser.