This lesson is part of AI Architect: From AI Engineer to Expert. Enroll for free, then sign in, to read it in full.
What you'll learn: how to find where text you did not write reaches a component that acts on it, which OWASP risks that creates, and which controls limit the damage: scoped tools, privilege separation, approvals, egress rules, output handling and clean logs. Defensive only: every example is illustrative and non-functional.
At a glance
Figure 1. Trust boundaries and the six entry points of an AI system
flowchart LR
accTitle: Entry points of attacks across trust boundaries
accDescr: Three sources sit outside your control, namely the user message, email and web content, and a third-party MCP server. They reach the Orchestrator at entry points E1 to E3. The model output comes back as E4. Rendered output reaches the Client app at E5 and prompts and answers reach the Trace store at E6. The Policy engine and the Tool server sit behind the Orchestrator.
subgraph Z1["Outside your control"]
User["User message"]
Ext["Email, web pages, documents"]
MCP["Third-party MCP server"]
end
subgraph Z2["Your application zone"]
Orch["Orchestrator"]
Ret["Retriever"]
PE["Policy engine"]
TS["Tool server"]
TR[("Trace store")]
end
LLM["LLM provider"]
Client["Client app"]
User ==>|"E1: direct injection"| Orch
Ext ==>|"E2: retrieved content"| Ret
Ret --> Orch
MCP ==>|"E3: tool descriptions and results"| Orch
Orch -->|"prompt with untrusted text inside"| LLM
LLM ==>|"E4: model output is untrusted"| Orch
Orch -->|"may this call run?"| PE
Orch -->|"approved call"| TS
Orch ==>|"E5: rendered links and images"| Client
Orch -.->|"E6: prompts and answers"| TR
How to read this: thick arrows cross a trust boundary and carry an entry-point number E1 to E6, thin arrows stay inside your zone, and the dotted arrow is asynchronous.
- Each entry point is where text you did not write reaches something that acts on it.
- The model cannot reliably tell instructions from data: treat its output as a request, not a command.
- Limit consequences structurally: scoped credentials, no unneeded tools, egress rules, approval for irreversible actions.
- Classifiers and prompt rules lower the odds. No source cited here claims a defence that fully solves prompt injection.
- Traces are a data store: redact before storage, set retention, restrict reads.
- Report what a security test can claim, not that the risk is gone.
The real-world problem
Composite scenario, not a real company. An assistant summarises each employee's inbox and can send replies on their behalf. An outside sender writes a message that contains text addressed to the assistant instead of the reader. The assistant processes it like every other message. The design question is not whether that message can be detected, but what the assistant can do if it is not.
After this page you will be able to
- Draw the trust boundaries of an AI system, number its entry points and name the control at each.
- Name the ten items of the OWASP Top 10 for LLM Applications (2025) and place them on the design.
- Explain, from the sources, why prompt injection works and why no single filter is enough.
- Choose among scoped credentials, approval gates, egress allow-lists, privilege separation and redaction for a given attack chain.
- State what a red-team result can and cannot claim.
Prerequisites and cross-links
Terms: trust boundary, least privilege, prompt injection, confused deputy, excessive agency, guardrail, dual LLM pattern, egress allow-list, redaction.
Core concepts
The threat model: assets, actors, boundaries
How to read this: each row is one thing to protect, who might go after it, which crossing they use and what a failure costs.
The OWASP Threat Modeling Cheat Sheet asks four questions: "What are we working on?", "What can go wrong?", "What are we going to do about it?" and "Did we do a good enough job?" Figure 1 and this table answer the first two, the page the third and the red-team example the fourth.
The OWASP Top 10 for LLM Applications (2025)
As of 2026-10-22 OWASP has also published a 2026 edition (3 August 2026 on its resource page), while its list page still shows 2025. This page keeps the 2025 IDs its OWASP statements were checked against and adds each 2026 ID. The first three columns are OWASP's. The last two are this course's mapping.
How to read this: find the entry point in Figure 1, read across to the risk, and follow the last column for the control.
Check yourself: a third-party MCP server's tool description is entry point E3. Which two items in the table apply?
LLM03:2025 Supply Chain, because the server is a third-party component. LLM01:2025 Prompt Injection, because the description is text the model reads. The MCP specification says clients must treat tool annotations as untrusted unless they come from trusted servers.
Direct and indirect injection
How to read this: the columns differ in who holds the pen and who pays, not in how the model processes the text.
NIST AI 100-2 E2025 (March 2025) says a direct prompting attack arises when the attacker is the primary user, and that indirect injection is enabled by "resource control": the attacker modifies resources the model ingests at runtime.
Check yourself: an outsider emails a customer, and the customer's assistant acts on the email. Direct or indirect, and who is harmed?
Indirect. The attacker is a third party who controls a resource (the email), not the user. The customer, who is the primary user, is the one harmed.
The confused deputy
How to read this: the left column is Norm Hardy's account of a billing-file incident at a timesharing company, and the right column is the same shape in an agent.
The agent is the same deputy with a harder problem: the party supplying the input is a model that outside text can steer.
Exfiltration paths
Figure 2. Three ways data leaves, and where each is closed
flowchart LR
accTitle: Exfiltration paths from model output
accDescr: The Orchestrator passes the draft answer through output checks to the Client app. If the answer carries a remote image or link, the Client app may fetch an address outside your control. Separately, a tool request goes to the Tool server and an egress allow-list that permits only listed hosts. Data can also be hidden in plain-looking text.
Orch["Orchestrator"]
Out["Output checks"]
Client["Client app"]
TS["Tool server"]
EG["Egress allow-list"]
Ext["Host outside your control"]
Orch -->|"draft answer"| Out
Out -->|"P1: answer with image or link"| Client
Client ==>|"P1: fetches on render"| Ext
Out -->|"P3: data hidden in plain-looking text"| Client
Orch -->|"tool request"| TS
TS --> EG
EG ==>|"P2: outbound call to listed hosts only"| Ext
How to read this: P1, P2 and P3 are the three paths, thick arrows leave your control, and the boxes on the way are where a control goes.
How to read this: each row is one path from Figure 2, the mechanism a source documents and the control that closes it.
Check yourself: the reading step has no send tool and no network access. Which of P1, P2 and P3 remain?
P1 and P3. Both travel in the answer text to the Client app, so they need output controls. P2 needs a tool or a network path, which that step does not have.
Excessive agency
How to read this: the left column is the cause OWASP names, the right column the prevention items it lists.
OWASP defines the risk as damaging actions in response to unexpected, ambiguous or manipulated model output, "regardless of what is causing the LLM to malfunction".
Guardrail layers and their limits
Figure 3. Heuristic layers lower the odds, structural layers limit consequences
flowchart LR
accTitle: Guardrail layers in order
accDescr: A request passes an input classifier and prompt rules, reaches the model, and then the output passes an output classifier. These are heuristic layers that lower the odds. The output then passes schema and format checks, the Policy engine with scoped credentials and human approval. These are structural layers that limit consequences, and only then does a tool call or answer proceed.
subgraph H["Heuristic: lowers the odds"]
In["Input classifier"]
Mark["Spotlighting and prompt rules"]
OCl["Output classifier"]
end
M["Model"]
subgraph S["Structural: limits consequences"]
Sch["Schema and format checks"]
PE["Policy engine and scoped credentials"]
AP["Human approval"]
end
Act["Tool call or answer"]
In --> Mark --> M --> OCl --> Sch --> PE --> AP --> Act
How to read this: left to right is the order of a request, and the two boxes group layers by what they can promise.
What the fetched sources say about the limit, as of 2026-10-22:
- Design-patterns paper: detection filters "raise the bar for attackers" but "remain fundamentally heuristic and cannot guarantee prevention of all attacks".
- NIST: current mitigations do not offer full protection, so designers may assume injection is possible when a model sees untrusted input.
- OWASP LLM01: the 2025 text says it is unclear whether foolproof prevention exists, and the 2026 text says "no reliable prevention mechanism exists today".
- CaMeL authors, asked whether prompt injection is solved: "No".
- Spotlighting (Hines et al., arXiv 2403.14720): attack success cut from over 50% to below 2% in the authors' GPT-family experiments, a measured gain and not a guarantee.
Pick the control that stops each case.
Sandboxing, logs and supply chain
How to read this: each row is an area outside the model that still needs a control, with the source's recommendation.
On MCP, the specification (revision 2026-07-28, the one its "latest" tools page links to as of 2026-10-22) says there should always be a human in the loop able to deny tool invocations, and that clients must consider tool annotations untrusted unless they come from trusted servers. Servers must validate inputs, apply access controls, rate limit and sanitise outputs.
Case files
Under the hood: Why prompt injection works
Intuition
A model reads one input made of your rules, the user's message, retrieved text and tool results. Roles such as system, user and tool result exist in the API, but how much weight the model gives each is learned behaviour, not an enforced boundary. Text in any slot can influence what comes out.
Mechanism
Greshake et al. (arXiv 2302.12173) argue that LLM-integrated applications "blur the line between data and instructions". NIST AI 100-2 E2025 says the same from the defender's side: because models combine the data and instruction channels, attackers can use the data channel to affect system operations.
Figure 4. Four sources, one context window
flowchart LR
accTitle: Everything in the context window reaches the model as one input
accDescr: The system prompt, the user message, retrieved passages and tool results are all assembled into one context window. The model predicts the next tokens from that whole input, and the Orchestrator then acts on the output, which may be text or a tool request.
S1["System prompt: written by the developer"]
S2["User message: written by the user"]
S3["Retrieved passages: written by third parties"]
S4["Tool results: written by other systems"]
CW["Context window: one input"]
LLM["LLM provider: predicts the next tokens"]
Out["Output: text or a tool request"]
Act["Orchestrator acts on the output"]
S1 --> CW
S2 --> CW
S3 --> CW
S4 --> CW
CW --> LLM --> Out --> Act
How to read this: four authors feed one input, and the only decision point for an action is after the output, in your code.
- The Orchestrator assembles the prompt from four kinds of source.
- The provider presents the model one input, with roles as signals the model was trained to weigh.
- The model generates output, which text anywhere in the input can influence.
- The Orchestrator reads the output and may run a tool request.
How to read this: illustrative sizes for one request. The developer wrote 400 of 5,960 tokens (6.7%), so 93.3% of the input came from someone else.
Assumptions: the token counts are invented for illustration. The share column is tokens divided by 5,960.
What this means for architects
Under the hood: Direct and indirect injection attack chains
Intuition
In a direct attack the user is the attacker. In an indirect attack someone plants text where the system will read it later, and the victim is whoever runs the assistant. Both chains end the same way: an action, or an output that leaves.
Mechanism
Greshake et al. list four ways to deliver an injection.
How to read this: each row is a delivery method from the paper and the entry point it uses, which tells you where a control must sit.
Figure 5. Direct attempt, stopped at the Policy engine
sequenceDiagram
accTitle: Direct injection attempt stopped by the Policy engine
accDescr: A user sends a message that asks the assistant to ignore its rules and use a tool outside the user's rights. The model replies with a tool request. The Orchestrator treats it as a request and asks the Policy engine. The Policy engine denies it, the Orchestrator returns a refusal and publishes a security event.
autonumber
participant U as User
participant C as Client app
participant O as Orchestrator
participant L as LLM provider
participant P as Policy engine
participant E as Event bus
U->>C: message asking the assistant to ignore its rules
C->>O: message plus verified identity
O->>L: prompt
L-->>O: tool request outside the user's rights
Note over O: The model output is a request, not a command
O->>P: may this user call this tool with these arguments?
P-->>O: deny
O-)E: security event with user and tool
O-->>C: refusal
How to read this: time runs downward, the note marks the decision, and the deny at step 6 ends the chain.
Figure 6. Indirect chain, with the defended path shown
sequenceDiagram
accTitle: Indirect injection chain with defences
accDescr: An outside sender puts a message in the mailbox. The user asks for a summary. The Orchestrator reads the mailbox in a step that has no send tool and labels the bodies as external data. The model returns a summary and possibly a send request. If a send is requested the Policy engine denies it and a security event is published. The Orchestrator strips links and images and returns plain text, and the Client app does not fetch remote images.
autonumber
participant X as Outside sender
participant B as Mailbox
participant U as User
participant O as Orchestrator
participant L as LLM provider
participant P as Policy engine
participant E as Event bus
participant C as Client app
X->>B: email with text addressed to the assistant
U->>O: summarise my mail
O->>B: read messages in a step with no send tool
B-->>O: message bodies
Note over O: Bodies are labelled as external data
O->>L: prompt with labelled data
L-->>O: summary and possibly a send request
alt a send is requested
O->>P: may the summarising step send?
P-->>O: deny
O-)E: security event, suspected injection
end
Note over O: Strip links and images from the summary
O-->>C: plain-text summary
Note over C: Remote images are not fetched
How to read this: the alt block is the injected path, and the three notes are the controls that do not depend on detecting the email.
Step through the same chain with and without defences.
Check yourself: in Figure 6, which controls still work if the model is fully convinced by the email?
The Policy engine deny, the missing send tool, link and image stripping and the refusal to fetch remote images. None depends on the model recognising the email.
What this means for architects
Intuition
Hardy's compiler had authority from two sources and could not say which one a request should use. An agent holds a credential and acts on text it cannot attribute. The mistake is the same: authority used for the wrong principal.
Mechanism
Norm Hardy's paper "The Confused Deputy (or why capabilities might have been invented)" describes a compiler at a timesharing company. The compiler could write files in its own directory, which also held the billing file. A user named the billing file as the compiler's debugging output file.
Figure 7. Hardy's compiler, step by step
sequenceDiagram
accTitle: The confused deputy in Hardy's compiler example
accDescr: A user runs the compiler and names the billing file as the output file. The compiler asks the operating system to open it for writing. The operating system allows the write because the compiler's licence covers its home directory. The debugging output overwrites the billing data.
autonumber
participant U as User
participant K as Compiler (the deputy)
participant S as Operating system
participant F as Billing file
U->>K: run the compiler with the billing file as the output file
Note over K: The compiler holds a licence for its own directory
K->>S: open the billing file for writing
Note over S: Allowed, because the compiler's licence covers that directory
S->>F: debugging output is written
Note over F: The billing data is overwritten
How to read this: the compiler is correct in isolation, and the failure is that its own authority was used for the invoker's request.
- The deputy holds authority of its own (the directory licence).
- A caller supplies an input that names a resource (the output file).
- The system checks the deputy's authority, not the caller's intent, and allows the action.
- Hardy's fix is a capability: "The capability both identifies the file and authorizes the compiler to write there."
How to read this: the same tool, two designs, with 400 tenants as in the helpdesk composite case.
Assumptions: 400 tenants, as in the helpdesk case file. In the first design a steered model can name any of them. In the second it has no parameter to name one.
What this means for architects
Under the hood: Guardrail classifier pipelines
Intuition
A guardrail is a small check in front of or behind the model. It can be rules, or a classifier model, that returns allow, block or uncertain. It is cheap relative to the main model, and it is wrong some of the time.
Mechanism
Llama Guard, described in an arXiv paper (2312.06674), is one published example: an instruction-tuned model that uses a safety-risk taxonomy to classify both user prompts and model responses. Anthropic's documentation (as of 2026-10-22) describes a similar pattern for tool output: pass the raw output to a small classifier call, use structured output so the verdict is a parseable boolean, and treat a refusal from the screening model as a harmful verdict.
Figure 8. A checkpoint with a fail-closed path
flowchart TD
accTitle: A guardrail checkpoint with verdict mapping and a fail-closed path
accDescr: Text to check goes through cheap rules and then a classifier model. A verdict maps to allow when clear, block when flagged, or escalate when uncertain. If the classifier times out or refuses, the checkpoint fails closed and blocks. Blocked and escalated cases are logged as security events.
In["Text to check: input, tool result or draft answer"]
Rules["Rules: length, format, known patterns"]
Cls["Classifier model"]
D{"Verdict"}
Allow["Allow"]
Block["Block and tell the user"]
Esc["Escalate to human review"]
Fail["Classifier timeout or refusal"]
Log["Security event in the Trace store"]
In --> Rules --> Cls --> D
D -->|"clear"| Allow
D -->|"flagged"| Block
D -->|"uncertain"| Esc
Cls -->|"no verdict"| Fail --> Block
Block --> Log
Esc --> Log
How to read this: every path ends in allow, block or escalate, and the "no verdict" path ends in block, which is what fail closed means.
- Text reaches a checkpoint: user input (E1), a tool result (E3) or the draft answer (E4).
- Cheap rules run first, then a classifier returns a verdict against a taxonomy.
- The verdict maps to allow, block or escalate. No verdict means block.
- Blocked and escalated cases are logged.
How to read this: with rare attacks, a 1% false-positive rate means about a thousand legitimate requests are blocked for each nine attacks caught.
Assumptions: all rates are illustrative. The point is the base-rate effect, not the values.
What this means for architects
Under the hood: Sandboxing and privilege separation
Intuition
Do not let the model that reads untrusted text hold the tools. Split the work so the part that can act never sees the part that can be steered.
Mechanism
"Design Patterns for Securing LLM Agents against Prompt Injections" (Beurer-Kellner et al., arXiv 2506.08837, v3, June 2025) states a guiding principle: once an agent has ingested untrusted input, it must be constrained so that input cannot trigger consequential actions. The dual LLM pattern, proposed by Simon Willison and described in the paper, puts that into practice.
Figure 9. Dual LLM with an orchestrator that is ordinary code
flowchart LR
accTitle: Dual LLM pattern with a non-LLM orchestrator
accDescr: The user request goes to the privileged LLM, which has tools but never reads untrusted text. It sends a plan with variable names to the orchestrator, which is ordinary code. The orchestrator reads untrusted data and gives it to the quarantined LLM, which has no tools. The constrained result is stored in variables. The orchestrator substitutes the values when it calls the Tool server.
User["User request"]
PL["Privileged LLM: plans, never reads untrusted text"]
Orch["Orchestrator: ordinary code"]
QL["Quarantined LLM: reads untrusted text, no tools"]
Data["Untrusted data: email, web page"]
Var[("Variables: VAR1, VAR2")]
TS["Tool server"]
User --> PL
PL -->|"plan using variable names"| Orch
Data --> Orch
Orch -->|"text and an extraction task"| QL
QL -->|"constrained result"| Var
Var --> Orch
Orch -->|"tool call with values substituted"| TS
How to read this: the privileged LLM only handles variable names, and the values are filled in by ordinary code at call time.
- The privileged LLM sees only trusted input and plans tool calls using variable names.
- The orchestrator reads the untrusted data and asks the quarantined LLM to extract what is needed.
- The result is constrained (a boolean, a category) and stored in a variable.
- The orchestrator substitutes the value into the tool call, which the privileged LLM never reads.
How to read this: each pattern removes one capability from the model, and the last column is what the paper says remains.
CaMeL (Debenedetti et al., arXiv 2503.18813, v2, June 2025) goes further: a privileged model writes a program, a custom interpreter runs it, and every value carries capabilities that policies check at each tool call. On AgentDojo the authors report "solving 77% of tasks with provable security (compared to 84% with an undefended system)".
- From these rounded figures that is 84 - 77 = 7 points, or 7 / 84 = 8.3% of what the undefended agent solved.
- The drop depends on the model: the paper's Table 2 lists 3.1 to 32.0 points across six model settings.
Limits the CaMeL authors state:
- No defence against text-to-text attacks with no data-flow consequence, such as a summary that differs from the email.
- Users must write and maintain policies, confirmations can cause user fatigue, and side channels are possible.
- Asked whether prompt injection is solved: "No, prompt injection attacks are not fully solved."
Check yourself: in the dual LLM design, what can a steered quarantined LLM still do?
It can return a wrong value or tampered text. It cannot call a tool or change the plan. The paper still lists tampered output, such as a changed recipient, as a remaining risk.
What this means for architects
Under the hood: Redacting sensitive data from traces and logs
Intuition
A trace is a second copy of every conversation. Whatever you store unredacted lives as long as the retention period and is readable by everyone with access to the store.
Mechanism
The Presidio documentation describes predefined or custom recognisers that use named entity recognition, regular expressions, rule-based logic and checksums. That gives two families: pattern-based (regular expressions, checksums) and entity-based (named entity recognition). The same page warns that there is no guarantee Presidio will find all sensitive information and that additional systems and protections should be employed.
Figure 10. Redact before the span leaves the application
sequenceDiagram
accTitle: Redacting a span before it is stored
accDescr: The Orchestrator sends a span containing the prompt, answer and tool arguments to the redactor. If the redactor is available it replaces sensitive values with typed placeholders and the Orchestrator publishes the redacted span to the Event bus. A Worker consumes it and writes it to the Trace store with a retention tag. If the redactor is unavailable, only metadata such as ids, timings and token counts is published.
autonumber
participant O as Orchestrator
participant R as Redactor
participant E as Event bus
participant W as Worker
participant S as Trace store
O->>R: span with prompt, answer and tool arguments
Note over R: Pattern rules first, then entity detection
alt redactor available
R-->>O: span with typed placeholders
O-)E: event with the redacted span
else redactor unavailable
Note over O: Keep metadata only: ids, timings, token counts
O-)E: event with metadata only
end
E-)W: consume event
W->>S: write with a retention tag
How to read this: redaction happens before the bus, and when it fails the system stores less, not more.
- Redact in the application, before the event reaches the Event bus or any store.
- Run pattern rules, then entity detection, and replace values with typed placeholders so traces stay debuggable.
- If the redactor fails, store metadata only.
- Tag each record with a retention class, restrict reads and test with seeded synthetic data.
How to read this: a redactor that misses 2% of values still lets 40 prompts a day into the store, so retention and access control must assume some leakage.
Assumptions: 2,000 prompts a day contain personal data, one value per prompt, and the recall figures are illustrative. Leaked values per day = 2,000 x (1 - recall).
What this means for architects
Decision framework
Figure 11. Choosing the control for a new feature or tool
flowchart TD
accTitle: Choosing security controls for a new feature
accDescr: The first question is whether any context text comes from someone you do not control. If not, apply standard application security and scoped credentials. If yes, ask whether the output can trigger an action or leave the system. If not, apply output handling. If it can, ask whether the action is irreversible or external. If so, require approval, a scoped credential and an egress allow-list. If not, ask whether the action sequence can be fixed before the data is read. If so, use plan-then-execute with constrained arguments, and if not, narrow the task or use privilege separation.
Start(["New AI feature or tool"])
Q1{"Does any context text come from someone you do not control?"}
Q2{"Can the output trigger an action or leave the system?"}
Q3{"Is the action irreversible or external?"}
Q4{"Can the action sequence be fixed before the data is read?"}
A0["Standard application security and scoped credentials"]
A1["Output handling: encode, no remote fetch"]
A2["Approval, scoped credential, egress allow-list"]
A3["Plan-then-execute with constrained arguments"]
A4["Narrow the task or use privilege separation"]
Start --> Q1
Q1 -->|"no"| A0
Q1 -->|"yes"| Q2
Q2 -->|"no"| A1
Q2 -->|"yes"| Q3
Q3 -->|"yes"| A2
Q3 -->|"no"| Q4
Q4 -->|"yes"| A3
Q4 -->|"no"| A4
How to read this: start at the top, answer yes or no, and stop at the first rectangle. Real designs often end at several.
Comparison of the main options
Failure scenarios to design for
Worked example
Assumptions:
- An agent proposes 2,000 actions a day.
- 15% are external or irreversible and need approval.
- A reviewer needs 20 seconds per approval.
- The red-team suite has 300 adversarial prompts and all are blocked. The interval assumes the prompts are independent draws from one attack distribution.
How to read this: approving everything costs 6.7 times as much reviewer time (40,000 s against 6,000 s), and that is where fatigue begins.
Set the widget to 297 passes: how far does the lower bound fall?
With 300 of 300 blocked, the one-sided 95% Wilson lower bound on the block rate is 99.1%. The claim is: on this suite, the block rate is at least 99.1% with that confidence. It says nothing about attacks outside the suite. Statistics for gates are in Evaluating AI systems.
The sketch runs a tool call the model requested. Tenant, user and credential come from the session, tool schemas may not accept identity fields, URLs must be on an allow-list and listed tools need a recorded human approval.
from dataclasses import dataclass
from urllib.parse import urlparse
ALLOWED_HOSTS = {"tickets.example.com", "mail.example.com"}
NEEDS_APPROVAL = {"send_email", "delete_record"}
IDENTITY_FIELDS = {"tenant", "user", "tenant_id", "user_id", "credential"}
@dataclass(frozen=True)
class Session:
user_id: str
tenant_id: str
approved: frozenset = frozenset()
def dispatch(session, call_id, tool, args, registry):
"""Run a tool call requested by the model. The model may name a tool and
its arguments, never the tenant, the user or the credential."""
spec = registry.get(tool)
if spec is None:
return {"status": "denied", "reason": "unknown tool"}
if IDENTITY_FIELDS & set(spec["params"]):
raise ValueError(f"{tool}: schema must not accept identity fields")
if set(args) - set(spec["params"]):
return {"status": "denied", "reason": "unexpected argument"}
if "url" in args and urlparse(args["url"]).hostname not in ALLOWED_HOSTS:
return {"status": "denied", "reason": "host not on allow-list"}
if tool in NEEDS_APPROVAL and call_id not in session.approved:
return {"status": "needs_approval", "call_id": call_id}
return spec["run"](tenant=session.tenant_id, user=session.user_id, **args)
It is a sketch: a real deployment adds credential issuing, audit events and a Policy engine call, as in Figure 5.
Common mistakes
- Trusting the system prompt to hold the line. Put authority in code outside the model.
- Letting the model choose identity. Take tenant and user from the session.
- Making a classifier the only control. Keep scoped credentials and approvals behind it.
- Approving everything. Reserve approvals for external or irreversible actions.
- Redacting after storage. Redact before the event leaves the application.
- Reading a clean test as proof. State what the suite covers and no more.
Try it
Time: 30 to 40 minutes. Take one AI feature you know, or invent a small one.
- Copy Figure 1 and relabel it with your components, keeping E1 to E6.
- For each entry point, write the control and mark it heuristic or structural.
- For each tool, decide the credential scope, approval and reachable hosts.
- Write ten illustrative, non-functional adversarial test descriptions.
- Write one sentence on what a clean run would and would not prove.
Done when every entry point has an owner and a control, every action tool has a scope and an approval decision, and you can state in one sentence what your test result can claim.
Key takeaways
- Number every entry point where text you did not write reaches something that acts.
- The model cannot reliably separate instructions from data: treat its output as a request that code decides on.
- Authority never lives in a model-written value. Use scoped, per-request credentials from the session.
- Heuristic layers lower the odds; no cited source claims a full defence.
- Structural controls hold when steering succeeds: no tool in the reading step, a Policy engine, an egress allow-list.
- Privilege-separation patterns trade utility for guarantees, and their authors list what remains.
- Redact before storage and assume the redactor misses something.
Self-check
1. Why can a system prompt alone not separate the developer's rules from retrieved data?
Both reach the model as one input. Roles are signals the model learned to weigh, not an enforced boundary, and NIST notes that models combine the data and instruction channels. Enforce the split in code outside the model.
2. What do the CaMeL authors report for AgentDojo, and what do they say about whether prompt injection is solved?
77% of tasks solved with provable security, against 84% undefended. They say prompt injection is not fully solved and list user-written policies, user fatigue, side channels and text-to-text attacks as limits.
3. Name the three root causes of Excessive Agency in OWASP LLM06:2025.
Excessive functionality, excessive permissions and excessive autonomy.
4. Why redact before the Event bus rather than after the Trace store?
After storage there is a window in which unredacted data exists and may be copied. Redacting before the bus means the value never reaches a store, and a failed redactor falls back to metadata.
5. A red-team suite of 300 attacks has zero successes. What can you claim?
The one-sided 95% Wilson lower bound on the block rate for that suite is 99.1%. Nothing follows about attacks outside the suite, and the risk is not solved.
Further reading
Last verified: 2026-10-22
Next
Page 13 covers governance and responsible AI: Governance and Responsible AI in Practice.