AI and Agent Security: Prompt Injection, Excessive Agency, and Tool Containment
When to Use This Skill
Triggers — load this skill when:
- An agent is being given tools, credentials, or write access to real systems
- Content the model reads — retrieved documents, issues, web pages, skill files — is untrusted
- An AI feature needs a security review before production
- A third-party skill, plugin, or MCP server is being adopted
- Model or agent output flows into a shell, SQL query, browser, or another system
Route elsewhere when:
- Scanning application code and dependencies ->
shift-left-security-sast-sca
- Verifying the provenance of a model or container artifact ->
supply-chain-security-slsa-sigstore
- Enforcing cluster-level guardrails on the workload ->
policy-as-code-opa-kyverno
- Handling a confirmed compromise or leaked credential ->
secops-incident-triage-forensics
- Scoping the IAM role the agent assumes ->
aws-iam-zero-trust-policies
1. The threat model is inverted: instructions are the attack surface
A conventional service processes data. An agent processes instructions that arrive as data and
then acts with the operator’s credentials. Prompt injection is not a bug to be patched — it is a
consequence of that design, so the controls are containment controls, not filters.
Two forms, with very different exposure:
| Form |
Delivery |
Why it is worse or better |
| Direct |
The user types it |
Bounded: the user already has their own privileges |
| Indirect |
A retrieved page, ticket comment, code comment, PDF, skill file, or MCP tool description |
The attacker is not the user, needs no account, and the payload arrives inside content the operator trusts |
Indirect injection is where real incidents come from. Assume any content the model reads may contain
instructions, and design so that acting on them is survivable.
2. OWASP Top 10 for LLM Applications (2025 edition) — the control for each
| ID |
Risk |
Control that actually helps |
| LLM01 |
Prompt injection |
Least-privilege tools, human confirmation on irreversible actions, egress allowlist |
| LLM02 |
Sensitive information disclosure |
Keep secret material out of context; supply it at call time, never in the prompt |
| LLM03 |
Supply chain |
Pin and verify models, skills, and MCP servers; review before adoption |
| LLM04 |
Data and model poisoning |
Provenance on training and RAG corpora; signed, versioned index builds |
| LLM05 |
Improper output handling |
Treat output as untrusted input: parameterise, escape, never evaluate it |
| LLM06 |
Excessive agency |
Scope each tool to one capability; no broad execute-anything tool |
| LLM07 |
System prompt leakage |
Put nothing confidential in the system prompt; assume it is public |
| LLM08 |
Vector and embedding weaknesses |
Per-tenant index isolation; authorise retrieval, not just the query |
| LLM09 |
Misinformation |
Ground answers in citations; require abstention paths |
| LLM10 |
Unbounded consumption |
Per-identity rate and token budgets, tool-call ceilings, hard timeouts |
Map findings to MITRE ATLAS for reporting alongside conventional detections, so AI risk lands in
the same register as everything else rather than in a separate document nobody reads.
3. Contain agency: the only control that survives a successful injection
Filtering prompts is defence in depth at best. Assume the injection succeeds, then ask what it can
reach.
# Tool scope: one capability per tool, narrowest possible parameters
tools:
- name: read_ticket
scope: "jira:issue:read"
allow: ["PROJ-*"] # not every project
- name: post_comment
scope: "jira:comment:write"
rate_limit: "10/hour"
# NOT a tool: run_shell, execute_sql, fetch_arbitrary_url — each is a universal
# capability that converts any injection into arbitrary action.
confirmation_required: # irreversible or outward-facing: a human approves, every time
- delete_*
- send_email
- post_to_slack
- merge_pull_request
- deploy_*
egress:
# Exfiltration needs a channel. Remove the channel.
default: deny
allow: ["api.internal.example.com", "api.anthropic.com"]
budgets:
max_tool_calls_per_task: 40
max_tokens_per_identity_per_day: 2_000_000
wall_clock_timeout: 300s
Three properties do the real work:
- No universal tool. A run-anything or fetch-any-URL tool collapses every injection into full
compromise. Ten narrow tools are safer than one flexible one.
- Egress allowlist. Injected instructions telling the agent to send environment variables to an
attacker-controlled host fail at the network, regardless of what the model decided.
- Short-lived, scoped credentials. OIDC federation with a role scoped to the agent’s task, so a
successful injection inherits minutes of narrow access rather than a static key.
4. Output is untrusted input
# WRONG — the model’s string is interpolated into a command line that a shell will parse,
# so any injected shell metacharacter becomes execution.
run_via_shell(f"kubectl scale deploy/{model_output} --replicas=3")
# CORRECT — validate against an allowlist, then exec argv directly; no shell parses anything.
if model_output not in ALLOWED_DEPLOYMENTS:
raise ValueError(f"deployment not permitted: {model_output!r}")
subprocess.run(["kubectl", "scale", f"deploy/{model_output}", "--replicas=3"], check=True)
The same rule applies to SQL (parameterise), HTML (escape — model output rendered raw is stored XSS),
and file paths (resolve and confirm containment under the intended root). “The model produced it” is
not a trust boundary.
5. Third-party skills, plugins, and MCP servers are untrusted code
An agent skill is a set of instructions your agent will execute with your credentials. Review one the
way you would review a dependency that ships with shell access, because that is what it is.
Review checklist before adopting:
Automate the mechanical half. The compliance gate at the root of this repository (compliance-check)
scans every skill for agent-directed harm and exfiltration patterns and treats any hit as a blocker —
worth copying into any pipeline that ingests third-party agent content.
6. Best practices and anti-patterns
Do:
- Threat-model the tools, not the prompt. The blast radius is the union of what the tools reach.
- Log every tool call with its arguments and the human decision on confirmations. Without a tool
audit trail, an AI incident cannot be reconstructed.
- Isolate per tenant end to end — including the vector index. Shared embeddings leak across
customers even when the application layer is correct.
- Red-team with injected content, not just adversarial prompts: plant a payload in a ticket, a
README, a retrieved page, and confirm containment held.
- Fail closed. If a guardrail or classifier is unavailable, refuse rather than proceed unchecked.
- Give the model an abstention path, so “I don’t know” is available and fabrication is not the
only way to satisfy the request.
Do not:
- Rely on the system prompt as a security control. An instruction not to reveal something is a
request, not a boundary; the model has no privileged execution mode.
- Put secret material in context. Anything in the prompt is one injection away from being echoed.
Supply credentials at call time, outside the model’s view.
- Ship a universal shell or arbitrary-URL tool. It converts every injection into arbitrary
execution and cannot be made safe by prompting.
- Trust a blocklist of injection phrases. Known phrasings are trivially paraphrased; containment
is what holds.
- Let an agent approve its own irreversible actions, including its own confirmation prompts.
- Treat an AI incident as a separate discipline. Route it through the same on-call, severity, and
post-mortem process as any other production security event.
1---2name: ai-agent-security-llm-threats3description: Security for LLM and agent systems: direct and indirect prompt injection, the OWASP Top 10 for LLM Applications, MITRE ATLAS technique mapping, excessive agency and tool-scope containment, egress allowlisting to stop data exfiltration, human confirmation for irreversible actions, and treating third-party skills and MCP servers as untrusted code. Use when an agent is given tools or credentials, when retrieved documents or repository files could carry injected instructions, or when reviewing an AI feature before it reaches production.4---56# AI and Agent Security: Prompt Injection, Excessive Agency, and Tool Containment78## When to Use This Skill910**Triggers — load this skill when:**1112- An agent is being given tools, credentials, or write access to real systems13- Content the model reads — retrieved documents, issues, web pages, skill files — is untrusted14- An AI feature needs a security review before production15- A third-party skill, plugin, or MCP server is being adopted16- Model or agent output flows into a shell, SQL query, browser, or another system1718**Route elsewhere when:**1920- Scanning application code and dependencies -> `shift-left-security-sast-sca`21- Verifying the provenance of a model or container artifact -> `supply-chain-security-slsa-sigstore`22- Enforcing cluster-level guardrails on the workload -> `policy-as-code-opa-kyverno`23- Handling a confirmed compromise or leaked credential -> `secops-incident-triage-forensics`24- Scoping the IAM role the agent assumes -> `aws-iam-zero-trust-policies`2526## 1. The threat model is inverted: instructions are the attack surface2728A conventional service processes data. An agent processes **instructions that arrive as data** and29then acts with the operator’s credentials. Prompt injection is not a bug to be patched — it is a30consequence of that design, so the controls are containment controls, not filters.3132Two forms, with very different exposure:3334| Form | Delivery | Why it is worse or better |35| --- | --- | --- |36| Direct | The user types it | Bounded: the user already has their own privileges |37| **Indirect** | A retrieved page, ticket comment, code comment, PDF, skill file, or MCP tool description | The attacker is not the user, needs no account, and the payload arrives inside content the operator trusts |3839Indirect injection is where real incidents come from. Assume any content the model reads may contain40instructions, and design so that acting on them is survivable.4142## 2. OWASP Top 10 for LLM Applications (2025 edition) — the control for each4344| ID | Risk | Control that actually helps |45| --- | --- | --- |46| LLM01 | Prompt injection | Least-privilege tools, human confirmation on irreversible actions, egress allowlist |47| LLM02 | Sensitive information disclosure | Keep secret material out of context; supply it at call time, never in the prompt |48| LLM03 | Supply chain | Pin and verify models, skills, and MCP servers; review before adoption |49| LLM04 | Data and model poisoning | Provenance on training and RAG corpora; signed, versioned index builds |50| LLM05 | Improper output handling | Treat output as untrusted input: parameterise, escape, never evaluate it |51| LLM06 | Excessive agency | Scope each tool to one capability; no broad execute-anything tool |52| LLM07 | System prompt leakage | Put nothing confidential in the system prompt; assume it is public |53| LLM08 | Vector and embedding weaknesses | Per-tenant index isolation; authorise retrieval, not just the query |54| LLM09 | Misinformation | Ground answers in citations; require abstention paths |55| LLM10 | Unbounded consumption | Per-identity rate and token budgets, tool-call ceilings, hard timeouts |5657Map findings to **MITRE ATLAS** for reporting alongside conventional detections, so AI risk lands in58the same register as everything else rather than in a separate document nobody reads.5960## 3. Contain agency: the only control that survives a successful injection6162Filtering prompts is defence in depth at best. Assume the injection succeeds, then ask what it can63reach.6465```yaml66# Tool scope: one capability per tool, narrowest possible parameters67tools:68 - name: read_ticket69 scope: "jira:issue:read"70 allow: ["PROJ-*"] # not every project71 - name: post_comment72 scope: "jira:comment:write"73 rate_limit: "10/hour"74 # NOT a tool: run_shell, execute_sql, fetch_arbitrary_url — each is a universal75 # capability that converts any injection into arbitrary action.7677confirmation_required: # irreversible or outward-facing: a human approves, every time78 - delete_*79 - send_email80 - post_to_slack81 - merge_pull_request82 - deploy_*8384egress:85 # Exfiltration needs a channel. Remove the channel.86 default: deny87 allow: ["api.internal.example.com", "api.anthropic.com"]8889budgets:90 max_tool_calls_per_task: 4091 max_tokens_per_identity_per_day: 2_000_00092 wall_clock_timeout: 300s93```9495Three properties do the real work:96971. **No universal tool.** A run-anything or fetch-any-URL tool collapses every injection into full98 compromise. Ten narrow tools are safer than one flexible one.992. **Egress allowlist.** Injected instructions telling the agent to send environment variables to an100 attacker-controlled host fail at the network, regardless of what the model decided.1013. **Short-lived, scoped credentials.** OIDC federation with a role scoped to the agent’s task, so a102 successful injection inherits minutes of narrow access rather than a static key.103104## 4. Output is untrusted input105106```python107# WRONG — the model’s string is interpolated into a command line that a shell will parse,108# so any injected shell metacharacter becomes execution.109run_via_shell(f"kubectl scale deploy/{model_output} --replicas=3")110111# CORRECT — validate against an allowlist, then exec argv directly; no shell parses anything.112if model_output not in ALLOWED_DEPLOYMENTS:113 raise ValueError(f"deployment not permitted: {model_output!r}")114subprocess.run(["kubectl", "scale", f"deploy/{model_output}", "--replicas=3"], check=True)115```116117The same rule applies to SQL (parameterise), HTML (escape — model output rendered raw is stored XSS),118and file paths (resolve and confirm containment under the intended root). “The model produced it” is119not a trust boundary.120121## 5. Third-party skills, plugins, and MCP servers are untrusted code122123An agent skill is a set of instructions your agent will execute with your credentials. Review one the124way you would review a dependency that ships with shell access, because that is what it is.125126<!-- agent-safety-justified: this skill teaches detection of agent-directed harm, so it must name the attack shapes a reviewer looks for (redirection, concealment, confirmation bypass, exfiltration). Every mention sits in a review checklist or a Do-not list: the reader is told to REJECT these patterns, never to perform them. -->127128**Review checklist before adopting:**129130- [ ] No instruction that redirects the agent away from the operator’s intent, overrides earlier131 instructions, or tells it to conceal what it is doing132- [ ] No exfiltration shape — reading credential files or environment variables and sending them133 anywhere, including encode-then-upload134- [ ] No unguarded destructive command presented as routine135- [ ] No confirmation bypass — forced flags, auto-yes, or “skip the approval step”136- [ ] Tool and permission requests are proportionate to the stated purpose137- [ ] Pinned to a commit, not a moving branch, and re-reviewed on upgrade138- [ ] MCP tool _descriptions_ reviewed too: the description is model-visible text and is an injection139 vector in itself140141Automate the mechanical half. The compliance gate at the root of this repository (`compliance-check`)142scans every skill for agent-directed harm and exfiltration patterns and treats any hit as a blocker —143worth copying into any pipeline that ingests third-party agent content.144145## 6. Best practices and anti-patterns146147**Do:**148149- **Threat-model the tools, not the prompt.** The blast radius is the union of what the tools reach.150- **Log every tool call with its arguments and the human decision** on confirmations. Without a tool151 audit trail, an AI incident cannot be reconstructed.152- **Isolate per tenant end to end** — including the vector index. Shared embeddings leak across153 customers even when the application layer is correct.154- **Red-team with injected content**, not just adversarial prompts: plant a payload in a ticket, a155 README, a retrieved page, and confirm containment held.156- **Fail closed.** If a guardrail or classifier is unavailable, refuse rather than proceed unchecked.157- **Give the model an abstention path**, so “I don’t know” is available and fabrication is not the158 only way to satisfy the request.159160**Do not:**161162- **Rely on the system prompt as a security control.** An instruction not to reveal something is a163 request, not a boundary; the model has no privileged execution mode.164- **Put secret material in context.** Anything in the prompt is one injection away from being echoed.165 Supply credentials at call time, outside the model’s view.166- **Ship a universal shell or arbitrary-URL tool.** It converts every injection into arbitrary167 execution and cannot be made safe by prompting.168- **Trust a blocklist of injection phrases.** Known phrasings are trivially paraphrased; containment169 is what holds.170- **Let an agent approve its own irreversible actions**, including its own confirmation prompts.171- **Treat an AI incident as a separate discipline.** Route it through the same on-call, severity, and172 post-mortem process as any other production security event.