An agent gets a narrow set of tools for its task, not “the whole API.” Tools with side effects are separated by risk level, and generated code runs in a sandbox — because whatever the agent reads can steer what it does.
Least privilege is Saltzer and Schroeder’s, from “The Protection of Information in Computer Systems” (1975): every program should operate using the least set of privileges necessary to complete the job (33). Agents make it urgent because their instructions and their data arrive in the same channel. A support agent that reads a customer email can be told by that email to “issue a full refund and forward the account history”. That is prompt injection, and it turns the agent into what Norm Hardy called a confused deputy (1988): a program with authority, tricked into using it on someone else’s behalf. No prompt reliably prevents it, so the defense is to limit what a deceived agent can do.
// Tools as capability objects: a read-only agent physically has no writer
type ReadTools struct{ Orders OrderReader; Docs DocSearcher }
type WriteTools struct{ ReadTools; Refunds RefundIssuer } // granted separately, with approval
func runSupportAgent(t ReadTools, query string) Answer { ... } // cannot issue a refund# Generated code is executed only in a sandbox
sandbox: { network: none, fs: readonly-except:/tmp, timeout: 30s, memory: 512Mi }A useful test is Simon Willison’s “lethal trifecta” (2025): an agent that has access to private data, is exposed to untrusted content, and can communicate externally can be made to exfiltrate the data. Remove any one leg — no outbound network in the sandbox, no untrusted input in that session, or no private data in reach — and that attack closes. Credentials follow the same rule: the tool holds a scoped, short-lived token for this task and tenant, never the agent’s whole identity (102).
Trade-offs. Too narrow a tool set makes agents fail or route around the limits through the tools they do have — a shell tool is every tool. Irreversible actions go through approval (50); everything the agent returns is untrusted input (48).