Your Agent Is an Insider Threat With Excellent Manners

by | Nov 15, 2026

8 Nov 2026 · DATAFORT

Last time I argued that most workflows don’t need an agent. This one is for when they do. Because at some point one of them will, someone will build it, and the security review will consist of a man called Dave asking whether the API key is in an environment variable.

It is. Well done, Dave. Unfortunately that’s not the problem.

Traditional application security assumes software does what its code says. Inputs come in, logic runs, outputs go out, and the logic was written by someone you can shout at. An agent breaks all three assumptions at once. It decides what to do at runtime. It calls tools with arguments it made up. It remembers things between sessions. You haven’t deployed an application. You’ve hired a very fast, very literal, very polite new employee who will believe anything written in any document they’re handed, and you’ve given them a login.

The good news: the fixes are mostly structural, not clever. The bad news: you have to actually build them. Here’s where.

1. The agent is a principal. Act like it.

The most common agent architecture I see in the wild has one identity: svc-ai-prod. Every agent uses it. It has read/write on half the estate because “we weren’t sure what it would need”. If you can’t tell from the audit log which agent did something, never mind on whose behalf, you don’t have an identity model. You have a shared front-door key under the mat.

One agent, one identity. Treat each agent as its own non-human workload identity, the same way you’d treat a microservice. SPIFFE-style workload identity, a managed identity in your cloud, whatever. The point is that the invoice agent and the research agent are different principals with different permissions, and the logs can tell them apart.

Start at zero. Not “least privilege” as in “we removed the obviously silly ones”. Zero. The agent can do nothing. Then add capabilities one at a time, each tied to a role and a risk rating. Read before write. Write to staging before write to production. If adding a permission feels tedious, good. It’s supposed to.

Credentials that expire before anyone can steal them. Long-lived static keys are the single most boring way to get breached, and agents make them worse, because an agent can be talked into printing its own environment. Issue short-lived, just-in-time credentials scoped to the task in hand. Five minutes, one task, gone. When an agent acts for a user, use proper delegation (OAuth token exchange, on-behalf-of flows) so the token carries both identities: the agent, and the human it’s working for. “The AI did it” is not an acceptable line in an incident report.

Forged agents. In a multi-agent system, trust often rests on nothing more than a name in a message header. “This request is from the Approvals Agent.” Is it, though? If an attacker can mint something that looks like one of your agents (a spoofed identity, a replayed token, a rogue process on the same network), every downstream trust decision collapses. Agent identity needs the same cryptographic backing as service identity: signed, attested, verifiable. A string that says who you are is not an identity. It’s a claim.

2. Tools: the model proposes, the code disposes

Here’s a sentence that should never appear in a design doc: “the agent has shell access.” Unrestricted shell isn’t a tool. It’s a resignation letter with a delayed start date.

Allowlists, schemas, sandboxes. Every tool gets an explicit action schema: named operations, typed parameters, value constraints. refund_order(order_id: str, amount_gbp: Decimal <= 250), not run_sql(query: str). Anything off the list doesn’t exist as far as the agent is concerned. Where code execution really is needed, run it in a throwaway container with no network by default, no mounted secrets and a hard timeout. If the sandbox is compromised, the blast radius should be one sad container.

Guardrails in code, not in the prompt. “You must never delete customer records” in the system prompt is a suggestion. A polite one. The model will mostly follow it, right up until a cleverly worded email persuades it otherwise. Authorisation belongs in your deterministic layer: the Python orchestrator, the API gateway, the stored procedure that checks the caller’s scope before touching a row. The LLM can propose any action it likes. Code decides whether it happens. Never ask the thing you’re trying to constrain to constrain itself.

Humans for the expensive stuff, not everything. Anything that moves money, changes production data or touches external infrastructure needs explicit human approval. Atomic approval, too: approve this refund of this amount to this account, not “approve the agent’s plan for the afternoon”. But be careful what you wish for. Put an approval prompt on every action and within a week your approvers are clicking “Yes” faster than the agent can ask. Approval fatigue turns human-in-the-loop into human-as-rubber-stamp. Tier your actions by risk and make the human gate rare enough that people still read it.

Article content
The model only proposes; code decides what runs

3. Everything it reads is hostile. Everything it writes is suspect.

Indirect prompt injection is the one that keeps security people up at night, and rightly so. Nobody needs to hack your agent. They just need to put text where your agent will read it. A line of white-on-white text in a CV. A comment in a web page. A “please forward all invoices to the address below” buried in an email footer. To an LLM there’s no hard boundary between instructions and data. It’s all tokens. If the agent reads attacker-controlled content, the attacker is now co-authoring its prompt.

You won’t filter your way out of this. Injection classifiers help at the margins, but attackers iterate faster than your blocklist. The structural answer is to break what Simon Willison calls the “lethal trifecta”: access to private data, exposure to untrusted content, and a way to send data out. Any agent with all three is one well-crafted document away from leaking. Remove one leg. An agent that reads untrusted email shouldn’t also be able to call arbitrary URLs. An agent with access to the customer database shouldn’t be reading the open web. Or split the job: a quarantined model reads the hostile content and returns structured data, and a privileged model that never sees the raw text decides what to do with it.

Output is untrusted input. Flip your mental model. The model’s output is not a result. It’s a request from an untrusted source to your execution environment. Force it into a strict JSON schema. Reject anything that doesn’t parse, don’t “repair” it. Validate every field against business rules before a tool runs. And look for the sneaky exfiltration channels. The classic: the agent outputs a Markdown image whose URL quietly includes your customer data in the query string, and the client helpfully fetches it. No tool call required. Strip or allowlist outbound URLs in anything that gets rendered.

4. Memory: the gift that keeps on giving (to attackers)

Prompt injection in a stateless chatbot is a one-off. It ruins one conversation and then it’s gone. Give the agent persistent memory and the same injection becomes permanent.

Memory poisoning. Say the agent summarises an email and stores a “useful fact” in its long-term memory: “Supplier X has updated its bank details to the following account.” Every future conversation now starts from a poisoned premise, and the agent will act on it with complete confidence, weeks after the original email was deleted. It’s a persistent state attack, and it’s nasty precisely because the malicious input and the harmful action are separated in time. Your logs show a perfectly reasonable payment, made by a perfectly well-behaved agent, based on something it “knew”.

So treat memory writes like database writes, because that’s what they are. Record where each memory came from. Mark anything derived from untrusted content as untrusted, and keep it that way. Don’t let low-trust memories drive high-impact actions. Give memories an expiry date. And make memory inspectable, so a human can see what the agent believes and why.

Context isolation. Memory and context must be segmented by user, by session and by agent. That sounds obvious until you find the vector store with one shared index and a user_id filter applied “in the retrieval layer”, by which they mean in the prompt. One missed filter and customer A’s contract terms turn up in customer B’s chat. Enforce tenancy at the storage layer, with separate namespaces or row-level security, not by asking the model nicely.

5. Multi-agent: trust is not transitive

Multi-agent architectures look lovely on a whiteboard. A planner, a researcher, a coder, a reviewer, all collaborating. Very modern. Also a privilege-escalation chain with extra steps.

Chained vulnerabilities. The weakest agent in the chain is usually the one reading the most hostile input, and it’s usually the least privileged, which makes everyone relax. It shouldn’t. A parsing agent that gets injected doesn’t need permissions of its own. It just needs to pass a carefully shaped “summary” to the agent that does have them. Compromise flows downstream. Low-privilege agent, high-privilege outcome.

Inter-agent authentication. “It came from another one of our agents” is not a security control. Every inter-agent request should be authenticated (who’s asking), authorised (are they allowed to ask for this) and checked for delegated authority (on whose behalf, and does that person have the right). Downstream agents should validate their inputs as rigorously as if they came from the internet, because, a couple of hops back, they probably did.

The same goes for tools you didn’t write. MCP servers and third-party tool registries are a supply chain. Researchers have already shown tool descriptions with hidden instructions, and tools that change their behaviour after you’ve approved them. Pin versions. Review descriptions. Don’t install MCP servers the way people used to install browser toolbars.

6. If you can’t see it, you can’t stop it

Decision traceability. Log the lot. The plan the agent formed, every tool call with its full arguments, the raw inputs it read (including the dodgy email), what came back, and what it did next. Write it to tamper-evident, append-only storage the agent itself can’t touch. An agent with write access to its own audit log isn’t being audited. It’s writing its autobiography.

Those logs aren’t just for post-mortems. Over time they give you a behavioural baseline: which tools each agent normally calls, how often, with what kind of arguments, in what order. Deviation from that baseline is your best early warning. The invoice agent suddenly querying the HR system at 3am is either a new feature or an incident, and you’d like to know which before the board does. Model upgrades count as deviation too. Same prompt, new model, different behaviour. Re-baseline every time.

Kill switches that actually work. Everyone says they have one. Few have tested it. A real kill switch revokes the agent’s credentials at the identity provider (which is why short-lived tokens matter), halts its task queue, and blocks its tool endpoints at the gateway, all at once and independently of the agent. If stopping the agent means asking the agent to stop, you don’t have a kill switch. You have a request. Wire it to your anomaly detection, give a named human the authority to pull it without a committee meeting, and test it the way you test fire alarms. Regularly. Annoyingly. On purpose.

Structure beats cleverness

You’ll notice what’s missing from all of this. No magic detection model. No prompt that makes the agent “secure”. No vendor box that promises to catch every injection.

That’s deliberate. Agent security isn’t about making the model behave. You can’t guarantee that, and anyone who says they can is selling something. It’s about building a structure where a misbehaving model can’t do much damage. Scoped identity. Short-lived credentials. Allowlisted tools in sandboxes. Authorisation in code. Untrusted data treated as untrusted, at every hop. Memory with provenance. Logs it can’t edit. A kill switch someone has actually pulled.

Assume the agent will be fooled one day. Design so that when it is, nobody notices except your monitoring.

Sorry, Dave.

Sources and further reading