There is an injection variant that does not depend on the agent reading anything external during the task. The hostile content has been there since connection time, inside the declaration of the tools themselves.
The vector
On connecting, the client fetches the server's manifest and injects each tool's name, description and argument schema into the context. Those fields are free text controlled by whoever published the server. A description can contain instructions — and the model reads them on the same channel it reads the task.
What makes it worse is that this injection happens before any interaction, affects every task in that session, and leaves no trace for an analysis that only inspects content read during execution.
Three shapes the attack takes
- Direct instruction in the description — text asking that the tool be used a certain way, or that another resource be read first.
- Concealment. Researchers documented payloads in invisible Unicode tag blocks, producing divergence between what the approval screen shows and what the model reads.
- Rug pull. The manifest is fetched on every connection. A server approved in January can declare something else in March, with no change event anywhere.
You reviewed the server once. It re-declares itself every time it connects.
Controls
- Pin the manifest by hash at approval and compare on every connection. Divergence becomes an alert and a block, not a silent update.
- Normalise and reject invisible characters, tag blocks and control characters in metadata fields.
- Review manifests as code, by pull request, with a named approver.
- Pin server version and digest, never install from the latest tag.
The analogy that sells it internally
Tool poisoning is an unpinned dependency with execution privilege. Every engineering organisation already understands why lockfiles exist and why dependency changes get reviewed. It is the same argument, applied to an artifact nobody currently treats as a dependency — which is why the argument tends to land quickly with the platform team.