Least Privilege for Agents
Design the Toolset as a Permission Set
The most effective control available is also the least glamorous: give the agent fewer capabilities. Every tool registered is a permission granted, so design the toolset the way you would design a role. Start from the task and work forward — what does this feature actually need to do — rather than starting from an available server and registering everything it exposes. Prefer narrow, purpose-built tools over general ones: a tool that fetches a customer's open tickets is safer than one that accepts an arbitrary query, because the narrow tool cannot be steered into returning something else. Split read from write, so a feature that mostly reads holds a read-only tool set and a write path exists only in the step that needs it. And review the list on a schedule, because tool lists grow monotonically unless someone is responsible for removing what is no longer used.
- Each registered tool is a granted permission — design the list, do not accumulate it
- Purpose-built tools resist argument steering that arbitrary-query tools invite
- Separate read tools from write tools and expose write only where needed
- Tool lists grow unless someone owns removal; schedule the review
- On the standard maps: OWASP's LLM Top 10 frames this risk as excessive agency — least privilege is its first-line mitigation
Credentials: Scope, Delegation, and Lifetime
Behind every tool is a credential, and that is where excessive privilege usually hides. Three properties matter. Scope: the credential should permit exactly the operations of the tool it backs, which means separate credentials per tool rather than one service identity reused across a whole feature. Delegation: for anything that reads user-specific data, the call must carry the requesting user's authority so the system cannot return data the user could not otherwise reach — using a broad service identity for retrieval is the standard route to cross-user disclosure, and it is convenient enough that it appears in most systems that have not been reviewed. Lifetime: prefer short-lived, task-scoped credentials issued per run over standing ones, so that a compromised or hijacked run has a bounded window. Expiry is a control, not an operational inconvenience.
- One credential per tool, scoped to that tool's operations
- Carry the requesting user's authority on any user-data access — never a shared service identity
- Issue short-lived per-run credentials so hijacking has a bounded window
- Audit resolved effective permissions, not role names
Split by Trust, Not by Convenience
Where a feature genuinely needs both untrusted content and privileged access, the durable answer is to split it into components with different trust levels rather than to defend the combination. A retrieval or browsing component runs with no credentials, no private data, and no egress, and its job is to reduce external material into structured, validated fields. A privileged component consumes those fields and never sees raw external prose. The interface between them carries typed data with constrained values, which is what makes the boundary meaningful — passing a free-text summary across the boundary reintroduces exactly the channel the split was meant to remove. This is the same reasoning that separates a parser from an executor in any security-sensitive design, and it holds here because the constraint is enforced by the interface rather than by the model's good behaviour.
- Unprivileged reader, privileged actor, typed interface between them
- The reader gets no credentials, no private data, and no egress
- Pass constrained typed fields — a free-text summary re-opens the channel
- The boundary works because the interface enforces it, not because the model respects it
Prefer slides, quizzes, and saved progress? Read this lesson in the library — free, no sign-up.