Least-Privilege Tool-Scoping Checklist for Production Claude Agents
Every tool you give a Claude agent is a capability an attacker could try to trigger through prompt injection. This checklist walks through scoping each tool down to the minimum it needs, before the agent goes to production.
How to Use This Checklist
- Work through it once per tool, not once per agent, a single agent with five tools needs this applied five times.
- Treat items in Tier 1 as blocking, don't ship a tool that fails one of them.
- Revisit this checklist whenever a tool's
input_schema, description, or backing permissions change, not just at initial launch. - Keep a record of the answer to each item per tool, this record is what a security review or SOC2 audit will ask to see.
Tier 1: Before You Define the Tool
- Name the exact capability the tool grants: Write one sentence describing what the tool can do in the real world, not what it's for. "Sends an email to any address" is the honest description of a generic
send_emailtool, even if its intended purpose is password resets. - Ask whether a narrower tool would work: A
send_password_reset_emailtool that only accepts auser_idand looks up the address internally is safer than a generalsend_emailtool, because the model never has an address parameter to manipulate. - Identify what data the tool can read: List every field, table, or document a tool's execution touches, including anything it reads internally to fulfill a call, not just what's in its
input_schema. - Identify what data the tool can write or change: Separately list every write, update, delete, or external side effect, this is usually the higher-risk half of the tool's surface.
- Decide if the tool needs write access at all: A large share of "action" tools can be redesigned as read-plus-human-confirmation, where Claude proposes an action and a human or a separate confirmation step executes it.
Tier 2: Scoping the Tool Definition
- Write a narrow
input_schema: Constrain types, use enums instead of free-text strings wherever the valid values are a known, finite set, and mark every field that must be present asrequired. - Avoid open-ended string parameters for anything that maps to a real action: A
query: stringfield on a database tool is far more dangerous than a set of specific, enumerated lookup parameters. - Write the tool description defensively: State explicitly what the tool must not be used for, in addition to what it's for, since the description itself shapes when the model reaches for the tool.
- Cap the blast radius of a single call: For tools that operate on multiple records, add an explicit limit (
max_records, pagination) so one call cannot touch an entire dataset. - Scope credentials per tool, not per application: Give each tool its own scoped API key, database role, or service account limited to exactly what that tool needs, never share one broad credential across every tool in the agent.
Tier 3: Enforcing Scope at Execution Time
- Validate every argument before executing, regardless of what the model claims: Treat arguments Claude passes as untrusted input, the same way you'd treat a value from an HTTP request body.
- Allowlist destinations for any tool that sends data externally: An email, webhook, or API-calling tool should check the target address or URL against an allowlist before sending, not trust whatever the model supplied.
- Enforce rate limits per tool, per session: Cap how many times a given tool can be called within a conversation or a time window, to bound the damage of a hijacked agent making repeated calls.
- Log every tool call with its arguments and outcome: Keep an audit trail (with PII redacted per your logging policy) that lets you reconstruct exactly what a tool did and why, after the fact.
- Fail closed on validation errors: If a tool's input fails validation, return an error the model can act on, don't silently coerce malformed input into something that "seems close enough" to execute.
Tier 4: Review Before Deployment
- Run an adversarial test pass: Attempt to get the agent to misuse each tool through a crafted retrieved document or user message, and confirm the scoping and validation hold.
- Confirm no tool has standing access beyond its stated purpose: Re-derive the credential scope for each tool from its
input_schemaand description, and flag any mismatch where the underlying access is broader than the tool needs. - Map tool access against the combined threat model: For each tool with write or external-communication capability, confirm what untrusted content (if any) could feed into a call to that tool, this is the multiplying risk factor from the threat model.
- Get sign-off from someone who did not write the tool: A second reviewer catches scope creep the original author has stopped noticing.
- Document a revocation path: Know, in advance, how to disable or restrict a specific tool quickly if it's found to be misused in production, without taking down the entire agent.
Applying the Checklist in Order
- Tiers 1-2 (items 1-10) happen at design time, before any code is written, and are the cheapest to get right early, reworking a tool's scope after launch means migrating callers and credentials.
- Tier 3 (items 11-15) is enforced in code, at the point the tool actually executes, and should be covered by automated tests, not just manual review.
- Tier 4 (items 16-20) is a gate before deployment, not a one-time step, repeat it whenever a tool's definition or backing permissions change.
Gotchas
- Treating the
input_schemaas the entire security boundary. A narrow schema helps, but if the backing credential or database role is broad, a validation bug or edge case can still reach data the tool was never meant to touch. Fix: scope the credential itself, don't rely on schema validation alone. - Sharing one API key or service account across every tool in an agent. This collapses the blast radius of the entire toolset into whatever that one credential can do, defeating the point of per-tool scoping. Fix: issue a separate, minimally-scoped credential per tool or per tool category.
- Writing a tool description that only explains the happy path. A description that says "sends a notification" without stating "only to addresses on the internal allowlist" gives the model, and any injected instruction, no signal about the intended boundary. Fix: state constraints explicitly in the description, not just in code.
FAQs
Do I need to run this entire checklist for a purely read-only tool?
Yes, but the stakes differ. Read-only tools still need Tier 1-2 scoping (what data can it read, is the schema narrow) and Tier 3 validation, since a read tool with access to sensitive data can still be an exfiltration path if its output ever reaches an untrusted channel.
Why does item 10 insist on a separate credential per tool instead of one shared credential for the whole agent?
A shared credential means every tool inherits the combined permissions of all of them, so a validation gap in any single tool exposes everything the shared credential can access. Per-tool credentials keep each tool's actual blast radius equal to its stated scope.
What's the fastest way to turn a broad tool into a narrow one?
Replace open-ended parameters (a free-text query, an arbitrary target address) with enumerated or internally-resolved values. A send_notification(user_id, template) tool that looks up the address internally is narrower than send_notification(to_address, message).
Should validation logic live in the tool's Python function or somewhere else?
It should live in the same function that executes the tool's side effect, as the first thing that function does, so there is no code path where an unvalidated argument can reach the real action. Centralizing it there also makes it easy to unit test independent of a live API call.
How is this checklist different from general input validation practices?
General input validation assumes a human or a trusted client is the source of input. Here, the "client" calling the tool is a model whose arguments can be influenced by content it read (including injected instructions), so validation has to assume the input is adversarial by default, not just malformed by accident.
What does "fail closed" mean in item 15, concretely?
def send_notification(user_id: str, template: str) -> dict:
if template not in ALLOWED_TEMPLATES:
raise ValueError(f"Unknown template: {template!r}")
# never silently substitute a default template here
return dispatch(user_id, template)Rejecting with a clear error, rather than silently defaulting to some fallback behavior, keeps the failure visible instead of quietly executing something unintended.
Is a rate limit per tool really necessary if the tool itself is narrowly scoped?
Yes. Narrow scope limits what a single call can do, a rate limit limits how many times it can be repeated. A tool that can only send one type of low-risk notification is still a problem if it can be triggered a thousand times in one session.
Who should be the "second reviewer" in item 19?
Anyone with enough context to evaluate the tool's actual capability, not just its intended purpose, and who didn't write the code, since the original author's mental model of "what this tool is for" makes it easy to overlook what it's actually capable of.
How does this checklist relate to the combined risk described in the security and RAG threat model?
Tool scope is one of the three multiplying factors in that threat model (untrusted content, tool access, permission scope). This checklist directly targets the last two, reducing what a tool can do and how broadly it's allowed to do it, independent of whatever defenses exist against the untrusted-content factor.
What is a revocation path, and why does item 20 ask for it in advance?
A revocation path is a fast, tested way to disable or restrict a specific tool (feature flag, credential rotation, config toggle) without redeploying the whole agent. Having it ready before an incident, rather than improvising during one, is what makes the difference between a contained issue and an extended outage.
Should this checklist be applied to internal, developer-facing agents too, or only customer-facing ones?
Internal agents still combine untrusted content (internal documents, tickets, code, other employees' input) with tool access, so the same risk factors apply. Internal-only reduces who could be an attacker, it doesn't eliminate the need for scoping.
Related
- Understanding the Claude Security and RAG Threat Model - why permission scope is one of the three multiplying risk factors.
- Secrets Handling and Preventing Data Exfiltration Through Tool Use - the specific defenses for credential and data-leak risk.
- Defending Against Indirect Prompt Injection in RAG-Retrieved Tool Results - reducing the untrusted-content factor this checklist doesn't cover.
- Security, Compliance & RAG Best Practices - the section-wide summary this checklist feeds into.
Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5 - and the official
anthropicPython SDK (latest 0.x release). Model names, pricing, and SDK versions move quickly - verify current specifics at platform.claude.com/docs before relying on them.