Agent Security Explained
← /learn · 04

Least privilege and scoped credentials

Give each task only the tools, scopes and destinations it needs, and tokens bound to one audience: the same plan under a broad grant and under its least privilege, and a call outside the task stopped and explained.

Loading the animation…

Concept

Least privilege is the oldest rule in security: give each piece of a system only the access it needs. For an agent the unit is the task. Before it runs, work out from its plan which tools it calls, which permission scopes those calls need, and which destinations it sends to, and grant exactly that. Anything outside the grant is denied by a rule that needs no detection and no judgement.

The animation runs one task, summarising the user's notes into a file, under two grants. The broad grant holds all 8 tools, every scope and every destination; the task needs 3 (read_notes, summarise, write_file), so 5 tools sit in the broad grant unused, including every tool that reads untrusted data and both ways to send data out (chapter 3's circles). The last call, sending the notes to an outside address, is not part of the task. Under the broad grant the only thing in its way is a rule about private data, which leaves it to the user to approve (ask; here the user refuses). Under least privilege it is deny, for 4 separate reasons: the tool is not granted, its scope is not granted, the destination is not granted, and private data would leave.

The same idea applies to credentials. Each server an agent calls gets its own token, minted for that server (its audience) with only the scopes the task needs there: the broad grant mints 6 tokens, least privilege 2. A token presented anywhere else is refused: the token is for notes-api, not mail-api: rejected. The MCP authorisation spec makes this a requirement: servers "MUST validate that access tokens were issued specifically for them as the intended audience" (MCP 2026-07-28, authorisation, using RFC 8707 resource indicators), and its security best practices call passing a client's token through to another service an anti-pattern (opened 2026-10-14).

Maths

A grant g=(Tg,Sg,Dg)g = (T_g, S_g, D_g) is a set of tools, scopes and destinations. The least privilege for a plan is the union over its steps of the tool, its scope and its destination; the plan runs under it with exactly the denials it meets under the broad grant (the engine's tests check this on every plan), and anything beyond it only adds calls the plan never makes. The rules are monotone: a smaller grant never allows a call a larger one denies, and a higher label never relaxes a decision (both tested on every tool, label and destination).

Least privilege bounds what a steered agent can do, not what it will try. With the broad grant, the agent holds all three circles of chapter 3; with the task's grant it holds only private data and a local write, so no flow out exists at all, whatever it reads.

Code

The token check, cut from the vendored engine (policy/capabilities.ts):

if (token.aud !== spec.aud) return { ok: false, reason: `the token is for ${token.aud}, not ${spec.aud}: rejected` };
if (!token.scopes.includes(spec.scope)) return { ok: false, reason: `the token lacks ${spec.scope}` };