The principle of least privilege applies to an agent's tools exactly as it applies to a human's system access: grant only what the current task actually needs, not broad access that happens to be convenient to set up once. A code-review agent doesn't need a delete-file tool; a read-only research agent doesn't need write access to anything; a customer-support agent that only ever needs to look up order status doesn't need a tool that can issue arbitrary refunds. Every tool you give an agent is a capability that a bug, a misjudgment, or an injected instruction (Lesson 6.1) could trigger — so the safest tool set is the smallest one that still lets the agent do its actual job.
Allowlist vs. denylist
An allowlist (only these specific actions are permitted, everything else is blocked by default) is the safer default for anything with real consequences — it fails closed. A denylist (everything is permitted except these specific blocked actions) fails open: anything you didn't think to list is allowed, including an action nobody anticipated at design time. Denylists are easier to write initially and easy to get dangerously wrong for exactly that reason — they require you to have already imagined every bad outcome in advance, which is a much harder bar than it sounds. New tools, new capabilities added by an MCP server update, or an unexpected combination of two individually-safe actions can all slip through a denylist that was correct on the day it was written.
This isn't a purely theoretical distinction — it maps directly onto how real systems configure agent permissions. Claude Code's own permission system, for instance, supports rules in three tiers: allow (runs without prompting), ask (prompts the user before running), and deny (blocked outright) — and the safe default for a new, unscoped tool is to fall into ask or deny, not allow, until someone has deliberately decided it belongs on the allowlist.
Key concept
When you're unsure which approach fits, ask what happens with an action nobody thought to list: an allowlist blocks it by default; a denylist permits it by default. For anything with real consequences, failing closed is usually the safer default.
Scoping beyond the tool name: parameters, credentials, and blast radius
Least privilege doesn't stop at "does this agent have the run_sql tool or not." A tool itself can be over-scoped even when its presence is justified:
- Parameter-level scoping. A
run_sqltool that accepts arbitrary queries is riskier than one restricted to a fixed set of parameterised, pre-approved query templates. An agent that "only needs to look up order status" is safer with a narrowget_order_status(order_id)tool than with a generalquery_database(sql)tool it's merely instructed to use carefully. - Credential-level scoping. The API key or database role behind a tool matters independently of the tool's description. If the tool's underlying credential has admin-level database access, a prompt-level instruction saying "only use this for reads" is not a real boundary — the actual enforcement has to happen at the credential (a read-only DB user, a scoped API token), because that's the layer an injected instruction or a model mistake can't talk its way around.
- Blast radius. Ask what the worst-case outcome is if this exact tool call executes with attacker-chosen parameters. A file-write tool scoped to one output directory has a small blast radius; the same tool with unrestricted filesystem access has an enormous one, even though "the agent has a write tool" sounds identical in both descriptions.
Static scoping vs. task-appropriate, session-scoped access
The tightest scoping isn't necessarily fixed for the lifetime of a deployment. A well-designed system can grant a broader tool set only for the duration of a specific session or task, and revoke it afterward — e.g. an agent doing a one-off data migration is temporarily granted write access to a specific table, then that access is removed once the migration completes, rather than left standing indefinitely "in case it's needed again." Static, permanent over-provisioning is a common real-world failure mode: access granted for a past task that nobody remembered to revoke becomes exactly the kind of unscoped capability an injection or a bug can later exploit, long after the original justification is gone.
Common exam distractor
Watch for answers that treat a tool's description or an instruction to the model ("only use this tool for read operations") as if it were an enforcement mechanism. A description tells the model what the tool is for; it does not restrict what the tool is technically capable of if called with different parameters, and it provides no protection against an injected instruction that persuades the model to call it differently. Enforcement lives in the tool's actual implementation, its underlying credential scope, and the allow/deny boundary around it - not in prose.