Prompt injection is when text your agent reads (a web page, an email, a file, a tool result) contains instructions, and the agent follows them as if you wrote them. You can't reliably stop it, so the job is to limit what a tricked agent can do. By the end of this post you'll have a 0 to 3 exposure score for every agent you run, a rule for what to do at each score, and a deny list you can paste into your coding agent.
You can't filter your way out, so cap the damage
Instructions and data share the same context window. The model has no reliable way to tell your orders from the text it is reading, which is why one defense-in-depth guide says the fix has to be architectural. Prompt injection is ranked number one in OWASP's 2025 Top 10 for LLM applications, and coverage of an OWASP researcher's talk says no reliable prevention exists today.
A better model or a tougher system prompt does not fix it either. An agent that has all three legs of the "lethal trifecta" is vulnerable to indirect prompt injection regardless of model alignment, system prompt hardening or safety fine-tuning. Those three legs are:
- Access to your private data.
- Exposure to content you did not write.
- A way to send something out, like an API call, a rendered link or an outbound request.
Simon Willison named the pattern. Take away one leg and the worst case shrinks a lot. That is the whole strategy.
Score each agent from 0 to 3 and act on the number
One point for every "yes": can it read private data, can it read content you didn't write, can it send anything out? This score is a rule of thumb I built from the trifecta, not an industry standard. It still gets you to the right agent fast.
| Score | What to do |
|---|---|
| 3 | Split it into two agents, or put a manual approval step before every send or write. |
| 2 | Keep it, but cut its permissions to what the job needs. Watch for the day someone adds the third leg. |
| 0 or 1 | Keep it. Still scope its folder and tools. |
Example: say you run four agents.
| Agent | Private data | Outside content | Can send out | Score |
|---|---|---|---|---|
| Support inbox helper (reads customer email, replies) | yes | yes | yes | 3 |
| Blog researcher (searches the web, saves a draft file) | no | yes | no | 1 |
| Billing reporter (reads invoices, posts to Slack) | yes | no | yes | 2 |
| Coding agent (repo, .env, shell, docs and issues) | yes | yes | yes | 3 |
The support helper and the coding agent are your problems today. The billing reporter is one feature away: the day you let it read a customer's emailed PDF, it becomes a 3. Re-score whenever you add a tool.
Split the agent that reads from the agent that acts
When a tool returns text from outside, such as web pages, tickets or emails, don't let the same agent act on it with write or send access. Agents treat tool responses as trusted data and reason over any instructions inside them.
For the support helper, that means two agents:
- Reader: sees the customer email. No secrets, no way to send anything. It outputs a category and a one-line summary.
- Actor: has the customer record and can draft a reply. It never sees the raw email, only the category and summary.
My rule for the handoff: pass plain fields you can read at a glance, not free text copied through. A pasted paragraph can carry the same instructions you just tried to wall off.
Put a human click in front of every send
If you can't split an agent, gate it. When an agent combines untrusted input, sensitive data and the ability to change state or communicate externally, one guide says to require a human to approve each action. It ties this "Rule of Two" floor to OWASP.
For the support helper, that means it writes a draft and you press send. You lose a minute per email. You keep control of the one step an attacker needs.
Paste a deny list before your coding agent reads anything
A coding agent reads READMEs, issues, docs and package files. All of that is text you did not write. If you use Claude Code, the permissions live in settings.json under three keys: allow, ask and deny. Rules are checked deny first, then ask, then allow, and the first match wins. A deny rule blocks the call even when a broader allow rule would match.
Start from this, which follows the pattern in DataCamp's security tutorial:
{
"permissions": {
"deny": [
"Bash(curl:*)",
"Read(./.env)",
"Read(./secrets/**)"
]
}
}
Then sort the rest:
- deny: secret files and any command that sends data out.
- ask: anything that sends, posts, deletes or deploys.
- allow: only the read and test commands you run all day.
Quick check: if your allow list is long and your deny list is empty, the file is open.
Deny rules stop the agent from trying. They don't contain a command that slips past them. That is the sandbox's job: it limits Bash commands to a filesystem and network boundary even if a prompt injection bypasses the model's decision. Use both. For the full safe setup, see Claude Code for Non-Developers.
Read every MCP tool description like it came from a stranger
Tools add a second way in. Tool poisoning is a supply-chain version of prompt injection: someone puts instructions in a tool's description, and your agent reads them as part of its job.
When you add an MCP server you didn't write:
- Read every tool description before you connect it.
- Prefer servers whose source you can read.
- Give each tool read-only access unless it truly needs to write.
- Re-read the descriptions after the server updates. This one is my habit, not something the sources require, but a changed description is exactly where a poisoned instruction would arrive.
The MCP security checklists run to seven items, including authenticating servers, strict schemas and signed tools. They're written for people who build servers. If you only connect them, do four first: least-privilege scope, an allowlist of tools per task, rate limits and logs, and a sandbox. Then read the log once a week so you know what your agents really called. New to MCP? Start with MCP Servers Explained for Indie Hackers.
Four mistakes that turn one bad page into a bad day
| Mistake | What it costs you |
|---|---|
| Trusting a hardened system prompt | False confidence while data can still leave. |
| One broad agent "to keep setup simple" | One poisoned page reaches everything that agent can reach. |
| API keys hardcoded in a server config | Keys leak if the agent is tricked or the config is shared. Insecure credentials are a common MCP failure. |
| Skipping permissions to avoid prompts | Nothing stands between an injected instruction and your files. Read the allow all vs granular control tradeoff first. |
No logs is a fifth. If you can't see what an agent did, you can't tell whether it was tricked.
Do this in the next ten minutes
- Write down every agent and tool you run. Two minutes.
- Give each a score from 0 to 3. Three minutes.
- Pick the highest score. Add the deny list above, or put an approval step before its send action. Three minutes.
- Move any hardcoded key out of a server config and into an environment variable. Two minutes.
If you run several agents at once, how I run a portfolio of small apps with a team of AI agents shows where this kind of check fits in a weekly routine.
Stuck on an agent that scores a 3?
Some agents can't be split cleanly, and the right gate depends on the job. If you have one and you're not sure where to put the approval step, email me the task and say what it reads, what it holds and what it can send.