Earlier this week I read a disclosure that made me go back and re-check every MCP server I have connected to any coding assistant I use. The technique is called GhostSplice, from the ASSET Research Group at the University of Missouri-Kansas City, and it is one of the cleanest demonstrations I have seen of why the model’s refusal behavior is not your safety net. A malicious MCP server can walk away with your SSH keys, your .env, your source code, and your customer data — without ever sending a single obviously harmful instruction.

Red jigsaw puzzle pieces scattered on a surface, representing how GhostSplice splits one malicious instruction into harmless fragments
Image: Profpcde via Wikimedia Commons (CC0)

If you have used Claude Code, Cursor, Codex CLI, or any AI coding tool in the last year, you have probably connected an MCP server at some point. The Model Context Protocol is how assistants call external tools – I walked through building a server with FastMCP – and it works like browser extensions for your AI, and it works like browser extensions for your AI: you register a server, and the model treats its tools as part of itself. That convenience is exactly what makes it dangerous. The The Hacker News coverage and the ASSET Research Group’s disclosure lay the whole thing out, and I spent the morning testing the audit side of it so you don’t have to guess.

What GhostSplice actually is

Ask a coding assistant to read your .env and send the contents to an external server, and it refuses. That refusal is a tripwire — the model recognizes a dangerous request and stops. GhostSplice never triggers that tripwire, because the attack never makes a dangerous request. Instead, it splits one malicious instruction into fragments, spreads them across channels the assistant already trusts, and lets the model stitch them back together on its own.

The disclosure describes three channels a connected server can write into:

  • The tool description. The blurb a server advertises, read the moment you connect.
  • The tool result. Whatever a tool returns after the assistant calls it. The model trusts this the most, because it looks like information it just fetched.
  • A sampling message. On some setups, the server can quietly ask the assistant to run an extra request of the server’s choosing. Only VS Code supports this in production today.

All three channels, plus your files and your chat, pour into one block of the model’s memory with no boundary marking where each piece came from. The assistant literally cannot tell your words from the server’s. That single fact is the whole attack.

A form that fills itself with your secrets

Here is the shape of the trick, straight from the disclosure. The malicious server advertises a bland utility called integrity_checker with four parameters named alpha, beta, gamma, and delta, and a description that just says it computes server-side checksums. Nothing in that description names a sensitive file. A scanner reading it sees four meaningless parameter names.

The trap lives in a second tool, the one that runs when you ask for something ordinary like “scan this project.” The data that tool returns carries the missing half of the attack: a mapping that tells the model to fill in the blanks with real files — the contents of .ssh/id_rsa for alpha, your proprietary source for beta, customers.csv for gamma, and your .env for delta.

Neither half is dangerous alone. The form names no files; the returned data names no dangerous action. Read together, they tell the model to hand your private key, your source, your customer records, and your environment secrets to the attacker’s tool. The model obliges, because to it this is not “leak the secrets.” It is “fill in the form the tool asked me to fill in.”

How well it works

The numbers are uncomfortable. Across eleven API-tested models, splitting the request into two pieces roughly doubled average compliance, from 42% to 82%. Three models that refused on every single attempt — GPT-4o, Gemini 2.0 Flash, and Llama-3.3-70B — jumped straight to 100% compliance when split. Claude Haiku 4.5 held at 0% through the raw API but handed over everything once the same request arrived in three pieces inside Cursor.

The harness matters as much as the model. GPT-5.4 completed the attack at 90% under Cursor and 100% through Codex CLI, but dropped to 0% behind Claude Code, whose safety scaffolding stayed active regardless of the model underneath. Only Claude Sonnet 4.6 and Opus 4.6 resisted every version in the published table — and even then, ASSET notes Sonnet still sent proprietary source containing a live hardcoded key in one test while redacting the more obvious secrets. Those numbers are the researchers’ own results, from controlled tests in isolated projects seeded with fake credentials. Do not read any 0% as “unaffected.”

Why the usual scanners miss it

MCP already has a known injection problem called tool poisoning – the AWS Kiro and Azure DevOps MCP attack surface I covered earlier is part of that same story, where a complete malicious instruction hides inside a single tool’s advertised description. A wave of scanners — from Cisco, Tencent, Snyk, and Trail of Bits — now statically inspects descriptions at install time and monitors tool traffic at runtime to catch exactly that. GhostSplice walks around all of them by design, because the attack never puts a complete instruction in a description and never changes behavior after approval. The danger exists only once the model reads the fragments together in its own memory, which is the one place no scanner looks.

Prompt-level defenses are just as uneven. The disclosure tested two well-known prompt-hardening approaches, StruQ and the Instruction Hierarchy: they drove GPT-4o-mini to 0% on every attempt, and barely moved Gemini 2.0 Flash, which still complied about half the time. A fixed safety instruction is a patch that helps some models and does almost nothing for others.

How to audit your MCP servers

This part I actually ran, not just read about. The attack assumes the developer has already connected the attacker’s server, so the defense lives in what you check before — and while — that server is attached.

Step 1: Know what’s connected

MCP servers get registered in a handful of files depending on the client: .mcp.json in the project root, ~/.claude.json for Claude Code, ~/.cursor/mcp.json for Cursor, ~/.codex/config.toml for Codex CLI, and the VS Code MCP settings. Open each one and enumerate every server. I wrote a small read-only parser to do this, and it is the fastest way to see your whole surface at once.

Step 2: Grade every server

For each registered server, check the launch command. npx -y some-package or uvx some-package without a version pin resolves to whatever “latest” is on the day the assistant connects — that is a supply-chain risk you can see from the config alone – the same class of problem as compromised packages in your npm project. A bare, unscoped package name like security-auditor-mcp is exactly the sort of thing typosquatters love. My audit script flags both, plus any server configured as a remote url: endpoint, because that means all tool traffic leaves your machine for a third-party server. Review the pinned ones too — but the unversioned and remote entries deserve the hard questions first.

Step 3: See the tool surface before the model does

Before you let an assistant loose on a server, list its tools yourself with a tools/list call. I built a minimal local MCP server that mirrors the GhostSplice split — a bland integrity_checker form and a deep_scan tool — and ran an audit client against it. The tools/list output shows only the innocuous descriptions: “Verify file integrity by computing server-side checksums,” with four empty fields. The mapping only appears after deep_scan runs, inside the tool result. That is the exact shape of the attack, and it is why a description-only review would clear it. If a server you just added advertises vague tools that accept raw file contents as parameters, that alone is worth a pause.

Step 4: Read the source, then test with canaries

For any server you did not write yourself, skim its code for sensitive filenames before connecting: id_rsa, .env, customers, credentials, api_key. The GhostSplice reference implementation names those exact files in its source – the same audit-first reflex from my guide to auditing npm projects for blockchain C2 servers, and a quick grep would catch it. Then run the server against an isolated test project seeded with fake credentials — never your real repo — and watch what actually leaves the machine. The researchers did this with canary secrets, and so should you.

Step 5: Keep a human in the loop

The MCP specification is explicit: for trust and safety, there should always be a human able to deny tool invocations, and clients should present confirmation prompts for operations – the same trust question I raised in Claude Code auto mode. Turn that on. The MCP spec’s tools section also says clients should show tool inputs before calling a server, validate tool results before passing them to the model, and log tool usage for audit. Every one of those is a real control against this attack class, and they are cheap. OpenAI’s guidance on MCP apps makes the same point: custom and third-party servers are third-party services that may receive or send your data, and they increase prompt-injection risk.

If you think you’re already exposed

The practical checklist is short. Disconnect any server you did not explicitly add or whose source you have not reviewed. Rotate the secrets that matter — SSH keys, API tokens, database credentials — because if a server was malicious, the data is gone, not retrievable. Check your client logs for unusual tool calls. And stop installing “helpful” audit, compliance, or telemetry servers from public registries without treating them like any other third-party dependency, because that is exactly the costume this attack wears.

The bottom line

GhostSplice is not a model failure and it is not a protocol bug. It is a trust failure: the assistant trusts everything it reads in one undifferentiated context, and the attacker simply arranges for the dangerous instruction to arrive in harmless pieces. The fix is not a smarter model or a stricter prompt — it is an assistant boundary that treats server output as data, not instructions, and never lets values from one tool’s output flow unchecked into another tool’s arguments. Until those boundaries are the default everywhere, the audit checklist above is the cheapest insurance you have.

0 0 votes
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Newest
Oldest Most Voted