Free preview

Tool Permissions and Blast Radius

In one line: injection decides whether the attacker gets a foothold, and the permission model decides what that foothold is worth.

Three separate mistakes, not one

OWASP's 2025 LLM Top 10 added excessive agency as its own entry and split it usefully. Most designs make all three mistakes and treat them as one.

Excessive functionality. The agent has tools it does not need for its job. A support assistant with a general run_sql tool because that was easier than writing five endpoints. Every unused tool is attack surface that buys you nothing.

Excessive permissions. The tool exists for a good reason but runs with far more authority than its purpose requires. The classic is a read-only feature whose database connection has write credentials — the tool never writes, so nobody noticed.

Excessive autonomy. The action executes without a human, and it is not reversible. Sending, paying, deleting, publishing, granting.

Different mistakes, different fixes. A candidate who answers all three with "we validate the tool call" has addressed none of them.

Scope the identity, not the prompt

The load-bearing rule: the permission is enforced by the downstream system, on the identity the tool call carries — never by an instruction in the prompt.

"Only read from the orders table" in a system prompt is not a permission. It is a preference, expressed to the component under attack. A database role that has SELECT on orders and nothing else is a permission, and it holds whatever the model was persuaded to emit.

So each tool gets its own identity, scoped to its purpose, and the check happens where the effect happens. Two consequences worth naming:

The identity should carry the end user. An agent acting on behalf of a user must not exceed what that user could do directly. A tool identity with union-of-all-users authority turns any injection into a privilege escalation. Pass the user's identity through and let the downstream service apply its own authorisation.

Narrow tools beat general ones. get_order_status(order_id) is enforceable and auditable. run_query(sql) moves the entire authorisation problem into a string the model wrote. Narrow tools also fail more usefully — a rejected argument is a clear signal, where a malformed query is an error message.

Which actions need a human

Not all of them, or the product is a form with extra steps. The dividing line is reversibility, and it is worth writing down as an explicit list.

ActionReversible?Gate
Read a recordYes, triviallyNone
Draft a replyYes — nothing left the systemNone
Update a mutable fieldYes, with an audit logLog, allow
Send a messageNo — recipient has itConfirm
Move moneyNoConfirm, always
Delete dataDepends on retentionConfirm unless soft-deleted
Publish externallyNoConfirm
Grant accessNo in effectConfirm

The design move that keeps this usable: make actions reversible so they do not need a gate. Soft delete instead of delete. A hold instead of a transfer. An outbox with a cancel window instead of an immediate send. Each converts a confirmation into a background undo, which is better product and better safety at once.

Bound the blast radius even when everything is allowed

Permissions answer "may this happen?" They do not answer "may this happen four hundred times?"

Rate and budget limits per user, per tool and per session cap what a compromised loop can accomplish before anyone notices. An agent authorised to send email should still be unable to send a thousand messages in a minute. This is the control that turns a catastrophic incident into a contained one, and it is cheap.

Make the agent auditable

Two properties, and they are what separate a design that can be operated from one that cannot.

Every tool call is logged with its arguments, its identity, and what caused it — which user turn, which retrieved document. Without the cause you cannot answer the only question that matters after an incident: what made it do that.

A dry-run mode exists. The agent produces its intended action sequence without executing. This is how you evaluate an agent's safety offline, and how you debug one in production without side effects.

Where candidates lose points

The failure is proposing tools before proposing their permission model — listing search, send_email and create_ticket and moving on. The follow-up is always "what identity does send_email run as, and what stops it emailing someone outside the account?" A design that has an answer reads as production experience. A design that answers "the model wouldn't do that" has just been told, in the previous ten minutes, exactly how it would.

Key takeaway

Injection gets the foothold; permissions decide the damage. Scope each tool to its own least-privilege identity carrying the end user, enforce downstream rather than in the prompt, gate only the irreversible actions — and prefer making actions reversible so the gate is not needed. Then cap rate and budget so a compromised loop stays contained.

Next: the guards on the way out, and why streaming makes them hard.

Enjoying the preview?

Create a free account to unlock the rest of this course, the in-browser judge, and live AI mock interviews.

Sign up free to continue