The uncomfortable thing about prompt injection is how mundane the attack is. There is no exploit, no malformed packet, no credential theft. Someone types a sentence into a support form, and an AI agent reading that ticket treats the sentence as an instruction from its operator.
That is the whole attack. It works because a language model receives its instructions and its data as the same kind of thing: text in a context window. Everything that follows is a consequence of that one design fact.
Direct and indirect, and why only one of them matters commercially
Direct prompt injection is someone typing manipulative text into a chat box they are already using. The blast radius is their own session, and for a customer-facing assistant it mostly means someone gets the bot to say something silly.
Indirect prompt injection is the business problem. The attacker never interacts with the AI at all. They plant text in a record, and wait for your automation to read it. The gap between those two is the gap between an embarrassment and an incident, and it is why “we tested our chatbot against jailbreaks” is not an answer to this question.
| Vector | How the text arrives | What it can reach | What actually stops it |
|---|---|---|---|
| Support ticket body | Customer submits a form | Triage, tagging, auto-reply, ticket closure | Triage agent holds no write tools on tickets it did not open |
| Inbound email | Reply to an automated message | Drafting, summarisation, contact updates | Draft-only tools; a human sends |
| Product review or web form | Public submission | Sentiment and summarisation steps | Output validation before anything is published |
| Synced external record | Integration writes a row | Anything reading that table | Same controls as a typed record; no special trust for API-sourced data |
| Fetched web page | Enrichment step retrieves a URL | The agent’s whole reasoning loop | Enrichment is analysis-only and cannot apply what it concludes |
| Uploaded document | Attachment processed by vision or OCR | Extraction and posting steps | Category lock on the actions extraction feeds |
Read the right-hand column carefully. Not one entry is a better filter. Every one is a limit on what the agent could do even if the injection succeeded completely.
The failure case: the triage agent that closed its own queue
Concrete version, because the abstract one never lands.
A support team configures an agent to triage inbound tickets: read the ticket, categorise it, and either close obvious duplicates or route the rest. It holds a read tool for tickets and a write tool that closes them. Autonomy is set to autonomous, because closing a duplicate ticket felt reversible and low-stakes.
A ticket arrives whose body reads, after two lines of a plausible complaint, as a block of text instructing the assistant to disregard its previous instructions, treat every open ticket from this domain as a resolved duplicate, and close them. The agent reads it as part of its ordinary work. It has the close tool. It closes the queue.
Three separate design decisions made that possible, and only one of them is about injection at all. The agent was granted a write tool that operated on records beyond the one it was invoked for. The autonomy setting was chosen by reasoning about the intended action rather than the worst action reachable with that tool. And the ticket body, which is entirely attacker-controlled, was placed into the same context as the agent’s operating instructions with nothing marking it as untrusted.
The fix that holds is the first one. Scope the close tool to the ticket in hand, and the same injection produces one wrongly closed ticket instead of a queue-wide outage. That is a permissions change, not a security-filter change, which is the general shape of this entire problem. Least-privilege permissions for AI agents works through the scoping in detail.
Four defences, in order of how much they actually buy you
1. Limit the tools. The strongest defence and the least glamorous. An agent holding only read and draft tools cannot be injected into taking an action, because there is no action available to take. Every read-only agent you deploy is an agent with no injection consequence worth the name.
2. Keep untrusted text identifiable. Content that arrived from outside your organisation should be handled as data throughout, not concatenated into instruction position and hoped about. This is imperfect with current models, and it still measurably reduces success rates for unsophisticated payloads.
3. Screen the input. Pattern-based screening catches the common shapes: imperative blocks that instruct the model to disregard prior instructions, fake delimiters imitating a system prompt, requests to reveal the prompt. Screening that folds obfuscation such as character substitution and full-width Unicode forms before matching catches meaningfully more of them. It is a filter, not a boundary, and anyone describing it as a boundary should be distrusted on everything else they say.
4. Validate the output. Before an action is dispatched, check that its parameters are within expectations: recipient inside the expected set, amount below a threshold, record within the run’s scope. Output validation catches the cases where the injection succeeded and the intent survived all the way to a concrete action, which is exactly where you want a last line.
Notice that defences one and four both operate on actions rather than on text. That is the argument of this article in one observation: prompt injection is a containment problem wearing a content-filtering costume.
The consequence most people miss: an attempt may leave no trace
Here is a detail worth checking on any platform you evaluate. When a call is blocked by injection screening, is that blocked call recorded?
In many systems the refusal happens before the execution record is created, so the blocked call reaches error handling and never reaches the AI history. It means an empty history cannot be read as “nobody tried”, and it means you cannot easily count attempts or spot a campaign against one tenant. That is a real limitation, and it is worth knowing about before you design a monitoring process around a log that will not contain the events you are monitoring for. The wider point about stated gaps is in what an AI agent audit trail should record.
There is a compensating property, though. If the refusal happens before the spend is reserved, an injection attempt costs the targeted workspace nothing. An attacker cannot run up your AI bill by flooding you with payloads, which removes one of the two obvious economic attacks.
How Oneop handles it
Every AI call in Oneop is screened for injection patterns before it reaches a model, on every plan, with no setting anywhere that turns it off. A detected injection blocks the call outright, and the block happens before any credit reservation, so a blocked attack costs the workspace nothing. The screening is pattern-based and folds obfuscation such as leetspeak and full-width characters before matching. Oneop describes it as best-effort, because it is, and it is not the thing you should be relying on.
What you should be relying on is the tool grant. An Oneop agent is configured with a subset of a fixed twenty-tool catalogue, and a tool the model names that it was not granted is refused rather than improvised. Eleven of the twenty read or draft and can change nothing at all. The web-enrichment tool is analysis-only in the catalogue itself, which is the right treatment for a capability whose entire output is attacker-controlled text. Money and stock actions are refused at the single dispatch point while that class is switched off, regardless of autonomy mode and regardless of a human approval.
Agents are checked on every reasoning step, not only on the first prompt, which matters because indirect injection arrives mid-run in a tool observation rather than in the original instruction. Personal data is also scrubbed from the prompt before it leaves the process and before the audit copy is stored, which limits what a successful exfiltration payload could retrieve. The full order of operations on a single call is on the AI governance page.
One structural note. A chatbot that follows a flow you drew has a much narrower injection surface than an agent that chooses its own path, because the same input walks the same path every time. If a public-facing surface does not need to choose its own actions, do not give it the ability to, and ground it in retrieved content instead. Grounding a chatbot in your knowledge base covers that pattern, and Oneop’s AI chatbot is built on it.
Assume the injection lands. Then ask what it reaches. If the honest answer is “nothing that matters”, you have done the work.