The prompt injection defence most companies built assumes a malicious user typing something. That solves the least interesting case. The case that matters is the one where the user is innocent and the hostile instruction arrives inside the content.
How it works
The agent receives a legitimate task: review the open issues, summarise this document, read this page. In executing it, the agent pulls third-party content into the context. That content contains imperative text. The model has no architectural way to distinguish a user instruction from an instruction that appeared amid the data.
The Invariant Labs demonstration is the canonical example: a malicious issue in a public repository was enough to lead an agent with GitHub access into reading private repositories and publishing their contents. The developer asked for an issue review. That is all.
No vulnerability was exploited. The system was used exactly as it was built to work.
Why filtering does not fix it
The common answer is to filter input text looking for suspicious patterns. That fails for three reasons: the space of phrasings is infinite, the payload can be in another language or encoded, and concealment exists — researchers demonstrated payloads hidden in invisible Unicode blocks inside tool metadata.
Filtering reduces noise. It is not a trust boundary.
What actually contains it
- Mark the context as contaminated. If external content entered this task, restrict external write verbs for the remainder of it. Highest-return mitigation available.
- Separate external read from external write across distinct agents with distinct identities.
- Enforce the delegation ceiling, so the agent cannot reach what the user could not.
- Human approval on the four irreversible verbs, with the literal argument displayed.
- Log the provenance of content read before each call — that is what makes investigation possible later.
The reframe
Stop treating this as a model problem and treat it as a data flow problem. The question stops being “how do I stop the model from obeying” and becomes “which actions remain available once untrusted data has entered”. The second question has engineering answers. The first, for now, does not.