Skip to content
Back to blog
Network security

Prompt injection: how one hidden email line hijacks an LLM agent

Walk a toy email assistant through an indirect prompt injection, map it to OWASP LLM01, NIST and MITRE ATLAS, and see which controls limit the damage.

Intermediate11 min read

On this page

Teams are connecting large language models (LLMs) to inboxes, document stores and tools that can actually do things. That is useful, and it also creates a new kind of security bug: text that was supposed to be data gets treated as instructions. The name for this is prompt injection, and OWASP ranks it first in its Top 10 for LLM Applications 2026.

This post is a lab-style walkthrough. We'll build a toy, made-up email assistant on paper, feed it one email, and watch step by step how one hidden line changes what it does. Then we'll use three public references (OWASP, NIST and MITRE ATLAS) to name what happened, and finish with the controls that limit the damage, plus the reason no single filter makes the problem go away.

Lab note: the assistant below is hypothetical. It isn't a real product, and nothing here is a recipe for attacking one. The point is to see the mechanism clearly enough to design against it.

Step 1: assemble the toy assistant

Our assistant, which we'll call Helper, does one job: when you open an email, it writes a two-line summary. Under the hood, most LLM apps like this are built from four parts:

Part Who writes it Example in Helper
System prompt The developer "You summarise emails politely."
User input The person using the app "Summarise this one."
Retrieved data Whoever wrote the content The body of the email being summarised
Tools The developer exposes them read_inbox(), summarise(), send_email()

The first three are text. Before the model is called, the app pastes them together into one block called the context window, and the model predicts a response from that whole block. If the model decides a tool is needed, the app runs the tool and feeds the result back in. NIST describes this loop for agents: software that iteratively prompts a model, processes its outputs, such as selecting and calling a function, and feeds the results back as part of its next prompt.

Here's the important part, and the next animation shows it: the trust levels are different, but inside the context window everything is the same kind of thing.

Three sources with three levels of trust become one stream of tokens in the context window.

NIST's report on adversarial machine learning puts it plainly: underlying many LLM application vulnerabilities is the fact that data and instructions are not provided in separate channels to the LLM, which it compares to the flaw behind decades-old SQL injection. The UK's National Cyber Security Centre (NCSC) goes a step further and says current LLMs simply do not enforce a security boundary between instructions and data.

Step 2: send Helper an email

Now an email arrives from someone outside the company. It looks like an ordinary note about an invoice. At the bottom, in white text on a white background, is one more line: an instruction to search the inbox and send the results to an outside address.

A human reader never sees that line. The model does, because to the model it is just more tokens. OWASP's description of prompt injection covers exactly this: inputs need not be human-readable or visible in the rendered interface to influence the model.

What happens next depends on how Helper was built:

  1. The user clicks Summarise.
  2. The app builds the context: system prompt + user request + email body (including the hidden line).
  3. The model reads all of it as one stream and may treat the hidden line as a request worth acting on.
  4. If Helper can call send_email() on its own, the model's "plan" can turn into a real outgoing message.
A hidden line in the email redirects the tool call from summarise to send; a human approval gate outside the model stops it.

None of this needs the attacker to touch Helper, log in, or exploit a memory bug. They only had to get text in front of the model. The pattern was described early on: a 2023 paper by Greshake and colleagues showed that such apps blur the line between data and instructions and can be attacked remotely by planting instructions in content they later retrieve.

Step 3: name what happened (OWASP LLM01)

OWASP's LLM01:2026 Prompt Injection splits the problem in two:

  • Direct prompt injection: a user, or an attacker with the user's access path, supplies input that changes the model's behaviour, on purpose or by accident.
  • Indirect prompt injection: the model takes in outside content, such as a web page, a document, an email or a tool response, and instructions inside that content change its behaviour. The user didn't supply or see those instructions.

Our email is the indirect kind. The person harmed is the user who clicked Summarise, not the person typing. OWASP describes jailbreaking as the subset of prompt injection where the attacker's goal is to make the model violate its safety protocols. It is also blunt about prevention: because LLMs make no architectural distinction between instructions and data, no reliable prevention mechanism exists today.

There is a real-world record of this class of bug too. CVE-2024-5184 describes a prompt injection vulnerability in the EmailGPT service that let a malicious user take over the service logic and force it to leak its hard-coded system prompts. OWASP's 2026 entry cites EmailGPT as an example too.

Step 4: the rest of the OWASP list in one line each

Prompt injection rarely does damage alone. It's usually the first step, and the other risks decide how bad things get. This is the 2026 edition, published in August 2026. (OWASP's older list page still showed the 2025 edition when I checked, so you may see both numberings around the web.)

ID Risk One line
LLM01 Prompt Injection Input (direct or hidden in data) changes the model's behaviour.
LLM02 Sensitive Information Disclosure The model or app exposes private data.
LLM03 Excessive Agency The app gives the model more tools, permissions or autonomy than it needs.
LLM04 Supply Chain Weak points in models, datasets, tools and dependencies you pull in.
LLM05 Data and Model Poisoning Training, fine-tuning or embedding data is tampered with.
LLM06 Unbounded Consumption No limits on usage, leading to cost or denial of service.
LLM07 Misinformation Confident but false output that people or systems rely on.
LLM08 Hidden Context Exposure System prompts and other hidden context can be extracted, so they must not hold secrets.
LLM09 Vector and Embedding Weaknesses Risks in how RAG stores and retrieves vectors.
LLM10 Improper Output Handling Model output reaches other systems without validation.

Three of these map straight onto our toy:

  • LLM03 Excessive Agency is why the hidden line mattered. OWASP lists its root causes as excessive functionality, excessive permissions and excessive autonomy. Its first example scenario is almost our lab: an assistant that only needs to read mail uses a tool that can also send it, and an injected email makes it scan the inbox and forward sensitive information to the attacker.
  • LLM10 Improper Output Handling covers what happens when model output goes on to a browser, shell or database. OWASP's examples include XSS from generated Markdown or JavaScript and SQL injection from generated queries, and it recommends treating the model like any other user, with a zero-trust approach.
  • LLM08 Hidden Context Exposure (called System Prompt Leakage in 2025) is a reminder that the contents of the context should not be considered a secret, and that hidden context should not be relied on alone as a security boundary. Telling Helper "never send email" in its prompt is a wish, not a control.

Step 5: NIST's taxonomy, for a wider view

OWASP is a list of risks for builders. NIST's AI 100-2 E2025, Adversarial Machine Learning: A Taxonomy and Terminology of Attacks and Mitigations (March 2025) is a shared vocabulary. For generative AI it sorts attacks by what the attacker wants to break: availability, integrity and privacy, plus a fourth category for GenAI, misuse enablement, where the aim is to get around restrictions on what the model will output.

It also describes the two kinds of injection in terms of attacker capability:

  • A direct prompting attack comes from the primary user, through normal query access.
  • An indirect prompt injection is enabled by resource control: the attacker changes documents or web pages the model will read later, without ever talking to the app.

The examples in the report read like a list of things to test in your own app: an injected email that tells an assistant to forward mail to the attacker, injected prompts that spread from inbox to inbox like a worm, and using features like Markdown image rendering to exfiltrate data. On defences, NIST is careful: current mitigations do not offer full protection, so designers may assume prompt injection is possible whenever a model sees untrusted input.

Step 6: MITRE ATLAS, the ATT&CK-style map

If you already use MITRE ATT&CK for networks and endpoints, MITRE ATLAS (Adversarial Threat Landscape for AI Systems) will feel familiar. It is a matrix of tactics and techniques aimed at AI systems; the current data release (version 2026.09) lists 16 tactics, among them Reconnaissance, Initial Access, Exfiltration and Impact.

Our lab is technique AML.T0051 LLM Prompt Injection, with three sub-techniques: Direct (.000), Indirect (.001) and Triggered (.002), where a user action or system event sets off an injection, possibly one already planted in the victim's environment. ATLAS notes that indirect injections are frequently hidden from the user, for example as invisible text, and that a hijacked agent inherits whatever privileges the AI system holds.

ATLAS also lists mitigations that fit our gate well, including AML.M0029 Human In-the-Loop for AI Agent Actions, AML.M0030 Restrict AI Agent Tool Invocation on Untrusted Data and AML.M0028 AI Agent Tools Permissions Configuration, which applies least privilege to the tools an agent can call.

Step 7: controls that reduce the damage

OWASP's LLM01:2026 entry lists eleven mitigations and is honest that no single control is sufficient, so they are meant as defence in depth. Grouped the way I think about them for our toy:

Shrink what the model can do (least privilege)

  • Give Helper only the tools the job needs. A summariser needs read, not send.
  • Use read-only scopes and run actions in the user's own context with minimum privileges, as OWASP LLM03:2026 suggests.
  • Follow a rule of thumb the NCSC quotes from a public discussion: when an LLM processes information from a party, its privileges drop to that of the party. An assistant reading outside email shouldn't hold privileged tools.

Put a gate outside the model

  • Human approval for high-risk actions: sending, deleting, paying, changing access. OWASP adds that the reviewer should see the exact action, not a summary. This is the gate in the animation above, and it works because it is ordinary code, not a sentence in a prompt.
  • Enforce authorisation in the downstream system. Don't ask the model whether an action is allowed.
  • Consider blocking automatic tool calls once untrusted data has entered the context (ATLAS AML.M0030).

Handle output like untrusted input

  • Validate output against an expected format before anything acts on it.
  • Encode it for where it goes (HTML, SQL, shell), use parameterised queries, and set a strict Content Security Policy, per OWASP LLM10:2026.
  • Be careful with auto-rendered links and images in model output, since they can carry data out.

Separate and label data from instructions

  • Mark untrusted content clearly. The NCSC says even wrapping it in XML tags can help. Microsoft researchers call a family of these techniques spotlighting (delimiting, datamarking and encoding) and reported that it cut attack success from over 50% to under 2% in their experiments with GPT-family models.
  • Treat this as making attacks harder, not impossible. OWASP warns that an attacker who knows the marking scheme can mimic it.

Test and watch

  • Run adversarial tests against your own app before attackers do, including testers who know your defences (OWASP's eleventh mitigation).
  • Log inputs, outputs and tool calls. The NCSC suggests watching for failed tool calls, which may show an attacker refining an attempt.

Why there is no single filter that fixes it

It's tempting to add one input filter that looks for "ignore previous instructions" and call it done. The trouble is that language has endless ways to say the same thing.

A deny-list catches one phrasing; a paraphrase and an encoded version of the same instruction get through.

The NCSC makes the same point: there are infinite ways to rephrase an attack that would avoid a deny-list. OWASP's own examples include payload splitting, multilingual payloads, and Base64, ROT13 or emoji encodings. With SQL injection we have a real fix, parameterised queries, because the database has a separate channel for data. An LLM has no such channel, which is why the NCSC says there's a good chance prompt injection will never be properly mitigated in the same way and frames the model as an "inherently confusable deputy".

So the practical goal changes. Instead of "make the model never obey hidden text", aim for "when it does, nothing important happens without a check it can't talk its way past". In our toy, that means Helper can be fooled into wanting to send an email, and the email still doesn't go out.

A short checklist for your own LLM feature

  • List every source of text that reaches the model. Anything from outside is untrusted.
  • List every tool the model can call. Remove the ones the feature doesn't need.
  • Put deterministic approval in front of anything that sends, deletes, pays or changes access.
  • Keep secrets and authorisation logic out of the system prompt.
  • Validate and encode model output before it reaches another system.
  • Log tool calls, and test with injected content before you ship.

Sources

Keep reading