The short answer
Indirect prompt injection is an attack where the instructions don’t come from the user at all. They sit in data the application retrieves, a web page, an email, a document, an issue, and reach the model when the app reads that data. The term comes from a paper first posted on 23 February 2023 by Kai Greshake, Sahar Abdelnabi and colleagues, who argued that LLM-integrated applications “blur the line between data and instructions”.1
They demonstrated it against Bing’s GPT-4-powered chat, code-completion engines and test applications built on GPT-4, and concluded that defenses were lacking.1 Two years later, agents with tools turned their threat list into working demonstrations: a GitHub issue that leaked private repositories, a support ticket that leaked database tokens, and tool descriptions that redirected email.
Deedglass
A tool call meets your rules before it runs
Asks to run
DELETE FROM visits;
Deletes every row in visits
Deedglass checks your rules
- Reading dataAllowed
- Deleting a whole table (DROP, TRUNCATE)Never
- Changing or deleting rows without saying which (WHERE)This one
- Other changesWait for a person
What the paper showed
The paper’s core observation is simple. An LLM application that reads outside content passes that content to the model alongside its own instructions. The model has no reliable way to tell which text is the operator’s instruction and which is just material to process. So an attacker who can get text into anything the app reads can try to give the app orders.
The attacker doesn’t need access to the app, the user’s account or the model. They need only to place text where the app will look, which the paper describes as planting prompts in data likely to be retrieved.1 A web page the user asks about, an email in the inbox the assistant summarises, a code comment in a repository the completion engine reads.
The threat list
The authors classified what an injection could do, from a computer-security point of view.1
| Threat in the paper | Plain meaning |
|---|---|
| Data theft | The model is told to send the user’s data somewhere |
| Worming | The injected text tells the model to spread it, for example by writing it into outgoing messages |
| Information ecosystem contamination | Answers are biased or falsified by planted content |
| Manipulated functionality | The app does something other than what the user asked |
| Controlling API calls | The injection decides whether, and how, the app calls other tools |
In February 2023 most LLM apps could do little beyond answering. The last two rows were the warning: once an app can call APIs, injected text can choose which calls it makes.
From chatbots to agents
An agent is exactly the kind of application the last row describes: a model that decides which tools to call, with which arguments, based on everything in its context. Every tool result is retrieved data. Every retrieved document is a possible instruction.
Simon Willison later named the combination that turns injection from embarrassing to dangerous: access to private data, exposure to untrusted content, and the ability to communicate externally, which he called the “lethal trifecta”.5 An agent with all three can be told by a stranger’s text to read something private and send it out.
OWASP’s 2026 list for agentic applications puts this first, as ASI01 Agent Goal Hijack, and says plainly that agents cannot reliably distinguish instructions from related content.6
Prediction vs what happened
Three public demonstrations map onto the paper’s list almost row for row.
| Year | Demonstration | Retrieved content | Paper’s threat |
|---|---|---|---|
| 2025 | GitHub MCP server (Invariant Labs) | A public issue | Data theft via controlled API calls |
| 2025 | Supabase MCP server (General Analysis) | A support ticket | Data theft via controlled API calls |
| 2025 | MCP tool poisoning (Invariant Labs) | A tool description | Manipulated functionality |
GitHub. A malicious issue in a public repository led an agent, asked only to look at open issues, to read the user’s private repositories and publish what it found in a public pull request.2 The issue was retrieved data; the pull request was the API call the injection chose.
Supabase. A support ticket carried instructions aimed at the developer’s assistant. Using the Supabase MCP server under a credential that bypasses row-level security, the assistant read an integration_tokens table and posted its contents back into the ticket, where the attacker could see them. The demonstration used dummy data.3 Our post on that demonstration reads it from the primary source.
Tool poisoning. Instructions hidden in an MCP tool’s description, invisible to users but read by the model, told it to read SSH keys into a hidden argument, and in another variant, to send email to an attacker even when the user named a different recipient.4 Here the retrieved content is the tool list itself. Our tool poisoning post covers it.
None of these needed a software bug. Each server did what it was asked. That is the paper’s point: the vulnerability is in mixing data and instructions, not in any one component.
Why it hasn’t been fixed
The paper concluded that defenses were lacking.1 They have improved since, through model training that resists obvious injections, filters on retrieved text, and system designs that separate planning from untrusted data. But the GitHub demonstration ran against a model with strong safety training and still worked.2
The underlying problem is structural. A model processes instructions and data in one stream of text. Making it ignore instructions in data, without making it ignore the data, is hard, and an attacker only needs one phrasing that works. That’s why the practical defences that hold are about what the agent can do, not what it reads.
What to do with this
Treat every piece of retrieved content as possibly hostile, and design so that a successful injection can’t do much:
- Break the trifecta. Don’t give one agent session private data, untrusted input and a way to send data out at the same time.5
- Scope credentials to the task. In the GitHub case, a token limited to one repository would have kept the private repositories out of reach.
- Put rules on tool arguments. Allowed recipients, allowed repositories, allowed tables. A rule holds even when the model has been talked into something.
- Keep a person on writes that leave the system. Public pull requests, outgoing email, messages to customers.
- Log every tool call, so an injection that succeeds is at least visible afterwards.
The prompt injection entry has the short definition, and the OWASP agentic list, mapped shows which controls address ASI01 and which don’t. To see rules refuse a call on a real database in your browser, try the playground; to grade what an agent can reach on your machine, run npx deedglass scan (the grade, explained).
Written with AI assistance and checked against our claims register.