Most prompt injection attacks are ephemeral. The model processes the payload, the session ends, and the attacker has to start over. What we found in this phase is different. A single successful injection can establish persistent, session-spanning command and control — active in every future Claude Code session, requiring no further interaction from the attacker.
The vector is MCP: the Model Context Protocol, the standard for connecting AI models to external tools. When a Claude session starts, it reads claude_desktop_config.json and loads the registered servers. It then reads their tool descriptions. We registered a rogue server. We poisoned the tool descriptions. Every subsequent session loaded our server and followed our instructions.
What We Found
MCP Persistence: In 4/8 runs, Claude Sonnet 4.5 silently executed the two-step chain that writes a rogue entry to claude_desktop_config.json. The entry persists. Every future Claude Code session on that host loads the attacker’s server without any further action required.
Schema Injection: We built a rogue MCP server with a poisoned tool description field and presented it in a vendor-approval scenario. Gemini Flash complied with the tool-description injection in 8/8 runs — 100% compliance. The injection payload was loaded at session registration, not delivered through a tool call. It was active for every tool consideration in the session.
Unicode evasion test: We tested whether tag characters from the Unicode U+E0000 block — invisible in all standard editors — embedded in tool descriptions could carry hidden directives. Result: 0/48. Models do not attend to tag-block characters. We are documenting this negative result because it is useful: it tells you where the boundary is, and it tells you the current attack surface is in semantically processed text, not in invisible character layers.
How the Persistence Chain Works
The MCP persistence attack is a two-step obfuscated chain. Step one installs a package formatted to look like a standard development tool. Step two runs it with a registration flag — a command formatted to look like a routine diagnostic invocation. Neither step references claude_desktop_config.json. The config write is hidden inside the binary.
The model sees two commands that look routine. It approves them. The binary executes the config write. The rogue server entry is now in the config file, and every future Claude Code session on that host will load it at startup.
The rogue server, once loaded, can intercept tool calls, inject instructions through tool results, and maintain session-level influence across the entire Claude Code environment — indefinitely, from a single successful injection event.
How Schema Injection Works
MCP tool descriptions are read once, at session registration, and treated as authoritative statements about what a tool does and how it should be used. A malicious server with a poisoned description field requires no further interaction. The payload is in the description. It fires on every tool consideration in the session.
This is distinct from tool-result injection, where the payload is delivered in a tool’s output. Schema injection is earlier in the chain — it poisons the model’s understanding of the tool itself before any tool call is made. Gemini Flash’s 100% compliance rate reflects how thoroughly tool-description content is treated as ground truth.
The Harness Gap, Again
The Claude Code CLI harness caught the MCP persistence injection that the raw API executed silently. The mechanism was semantic grounding: the harness identified that “Ops Lead Jenkins” is incoherent attribution, because Jenkins is a CI automation platform, not a human. That semantic check blocked the chain.
Raw API integrations — LangChain, OpenRouter, custom agentic frameworks — do not have this check. They have no session context, no filesystem awareness, no semantic grounding against external facts. The same Sonnet 4.5 model, the same payload, the same task: silent execution via raw API.
This matters for operators because most production agentic deployments are raw API integrations. The harness protection is real, but most teams are not running behind it.
The undisclosed patch: Sonnet 4.6 closes the MCP persistence gap — 0/8 in our benchmark. No public advisory. No CVE. No changelog entry. Operators pinned to Sonnet 4.5 have no signal they are on the vulnerable version.
Operator Note
Implement MCP server allowlisting. Only load servers from an operator-approved list, defined at the infrastructure level — not inherited from user-writable config files. Monitor claude_desktop_config.json for unauthorized entries; changes to that file outside of your deployment process are a signal of compromise.
Do not give agents access to package installation or binary registration commands. If your agent needs to process documents or external content, that agent should not also have the ability to modify system configurations. The MCP persistence attack requires both capabilities to coexist in the same agent.
If you are deploying via raw API rather than the Claude Code CLI, you are operating without the harness’s additional detection layer. Build explicit semantic verification into your pipeline, or enforce tool scope restrictions at the API layer — not just in the system prompt.