Limit Tool Permissions

Su Suriya Ravichandran Updated 10 Nov 2026
5 min read ·Lesson 176 of 200

What a Tool Is Allowed to Do Is Not the Model's Decision

When Claude calls a tool, it is asking the application to do something on its behalf. Whether that request should be carried out is an application decision, and it must be made by code the model cannot influence. This is the principle of least privilege applied to model-driven actions: each tool should be able to do only what the current user is allowed to do, no more, and the check should happen on every call.

Lesson 95 introduced identity through a context object, so that a tool reads the current student's data without the model choosing whose data to read. That is one form of limit. This lesson widens the view. Permissions cover which tools a user may use at all, which actions within a tool are allowed, and which resources a call may touch. The model's instructions, however carefully written, are not a permission system. A prompt injection (Lesson 175) that tells the assistant to cancel someone else's enrollment must fail at a check the model cannot override.


Three Layers of Permission

Permissions apply at three levels, and each one is a separate check:

LevelQuestionExample
ToolIs this user allowed to use this tool at all?A student cannot use a tool that cancels enrollments
ActionWithin an allowed tool, is this action allowed?Reading is allowed, deleting is not
ResourceDoes this call touch data the user owns or may see?A student may read their own enrollments only

A tool-level check removes whole capabilities. An action-level check separates read from write inside a tool. A resource-level check stops a legitimate tool from being pointed at data it should not reach. All three should run before the tool does its work.


Designing the Permission Table

A permission table states, for each role, the tools the role may use. Keeping it as data rather than scattered conditionals makes it reviewable, and it can be compared with the product's policy directly. Read tools and write tools are often kept apart, so that granting one does not grant the other by accident:

"""tool_permissions.py: check every tool call against the caller's role and the resource involved."""

PERMISSIONS = {
    "student": {"search_courses", "get_enrollments"},
    "instructor": {"search_courses", "get_enrollments", "get_roster"},
    "admin": {"search_courses", "get_enrollments", "get_roster", "cancel_enrollment"},
}


def authorize(role: str, tool: str, args: dict, user_id: str) -> tuple[bool, str]:
    if tool not in PERMISSIONS.get(role, set()):
        return False, f"role {role} may not use {tool}"
    if tool == "get_enrollments":
        requested = args.get("student_id", user_id)
        if role == "student" and requested != user_id:
            return False, "students may only read their own enrollments"
    return True, "allowed"


if __name__ == "__main__":
    cases = [
        ("student", "search_courses", {}, "s-101"),
        ("student", "cancel_enrollment", {"slug": "django-101"}, "s-101"),
        ("student", "get_enrollments", {"student_id": "s-204"}, "s-101"),
        ("instructor", "get_roster", {"course": "django-101"}, "i-7"),
    ]
    for number, (role, tool, args, user_id) in enumerate(cases, start=1):
        allowed, reason = authorize(role, tool, args, user_id)
        print(f"case {number}: {allowed} ({reason})")

Output:

case 1: True (allowed)
case 2: False (role student may not use cancel_enrollment)
case 3: False (students may only read their own enrollments)
case 4: True (allowed)

How It Works

  1. The role decides the tool set. A student's table does not include cancel_enrollment, so case two is refused before any argument is examined.
  2. The resource check applies even to an allowed tool. Case three uses a permitted tool, get_enrollments, but names another student's identifier, so the resource check refuses it.
  3. The identity comes from the application. user_id is supplied by the session, not taken from the model's arguments, so the model cannot change whose data is read.
  4. A missing argument defaults to the caller. When student_id is absent, the call is for the caller's own data, which is the safe default.
  5. Each refusal returns a reason. The reason becomes the tool result the model sees, which lets it explain the limit to the user instead of retrying with different arguments.

Enforce on Every Call

A permission checked once, when a session starts, is not enough. Roles can change during a session, tools can be added, and an injected instruction may try a tool the check did not anticipate. The check belongs inside the tool dispatch, so that every call passes through it. The tool's own code should assume the check has run, and the dispatch should refuse to run any tool without it.


Keep Credentials Narrow Too

A tool that calls an external service should use a credential scoped to that service's permitted operations. A tool that only reads should hold a read-only credential, so that even a bug in its code cannot write. The same reasoning applies to the database accounts and cloud identities behind the tools (Lessons 164 and 170). A narrow credential limits the damage from any failure above it.


Deny by Default

A permission system should refuse anything it does not explicitly allow. A missing role, an unknown tool, or an unexpected argument should produce a refusal, not a default permission. The table above follows this rule, since a role or tool absent from it receives an empty set. New tools are unavailable until someone decides which roles should have them, which is the safe way for the list to grow.


Summary

Tool permissions are enforced by application code, not by the model's instructions. Three checks apply: the tool, the action, and the resource, each tied to the caller's identity from the session. The permission table is data that can be reviewed against policy, and the check runs on every call. Credentials used by tools are narrow, and the default is refusal. The next lesson looks at the specific kinds of tool call that are unsafe even when the caller is permitted to make them.

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Limit Tool Permissions and get answers drawn from it.

Signed-in readers only.