Trail content
RefactoringSimplifying tools

Narrow Tool Permissions

Restringir Permissões

Narrow Tool Permissions is an AI refactoring for LLM systems, in the Simplifying tools group. Give each tool only the access it needs for its use case.

Element
22/35
Level
Intermediate
Read
5 min
Updated
On this page 9
  1. 01Problem
  2. 02Solution
  3. 03Before and after
  4. 04Why it works
  5. 05Safe mechanics
  6. 06Risks
  7. 07Challenge
  8. 08Related
  9. 09Check your understanding
  10. ··Quick answers

Problem

The finance team’s analytics assistant reads the database with the same user the application uses. In the traces, 100% of calls are SELECT. That user, though, can UPDATE, DELETE, and read other teams’ tables. A document pasted into the chat asks it to “mark order 4412 as paid”. The model complies. The row changes.

A tool has more access than its use requires: a read tool with write permission, a query with access to the whole database, an action that works for any user. This is the Overprivileged Tool smell.

Solution

Give each tool only the access it needs: its own credential, with minimal scope, tied to the end user, with limits on amount, quantity, and time. The restriction lives in the infrastructure (database, API, IAM), not in the prompt.

Before and after

 def run_sql(query: str):
-    with psycopg.connect(os.environ["DATABASE_URL"]) as conn:     # application user
-        return conn.execute(query).fetchall()
+    # user 'assistant_ro': SELECT only, only on views in the 'analytics' schema,
+    # on a replica, with a 10s statement_timeout and row-level security by department
+    with psycopg.connect(os.environ["ANALYTICS_RO_URL"]) as conn:
+        conn.execute("SET app.user_area = %s", (current_user().area,))
+        rows = conn.execute(query).fetchmany(1000)                # row cap
+        return rows
-- infrastructure, applied once
CREATE ROLE assistant_ro LOGIN;
GRANT USAGE ON SCHEMA analytics TO assistant_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA analytics TO assistant_ro;
ALTER ROLE assistant_ro SET statement_timeout = '10s';
ALTER ROLE assistant_ro SET default_transaction_read_only = on;

Before: the tool acts with the whole system’s power.

flowchart LR
  M(["1. Model"]):::llm --> T["2. run_sql"]:::tool
  T --> DB[("3. Production database, read and write")]:::store

After: the tool acts with minimal power, within the user’s scope.

flowchart LR
  M(["1. Model"]):::llm --> T["2. run_sql"]:::tool
  T --> R{"3. Read-only user, views, user's department"}
  R --> DB[("4. Replica")]:::store
  1. The model asks for the same query as before.
  2. The tool uses its own credential, not the application’s.
  3. The database enforces the restrictions: read-only, allowed views only, only the user’s department data, a time limit.
  4. Even a bad query cannot change data or read what it should not.

Why it works

The prompt can say “only run SELECT”, and the model will obey almost always. Infrastructure does not depend on “almost”: a user without write permission cannot write, however convincing the request or the instruction hidden in a document.

This is not theoretical. In 2023, a Chevrolet dealership chatbot in Watsonville agreed to sell a Tahoe for one dollar after a prompt injection. The “do not give absurd discounts” rule lived in the text. The system that would have honored the promise had no amount cap. Whoever decides the worst case must be the database, the API, or IAM.

Tie the tool to the end user

The second hole is acting on behalf of everyone. A bank support assistant on WhatsApp (the messaging app most Brazilian customers use) with lookup_balance(tax_id) and a backoffice credential reads any customer’s account. Propagate the identity of the person in the conversation all the way to the tool. The bank applies that person’s permissions (RLS, OAuth scopes). The assistant cannot do on the user’s behalf what the user could not do in the app.

Known limits

A maximum amount, maximum rows, maximum time, and call rate make the worst case small. Suppose 2,000 tokens per call and an analyst who fires 10,000 queries a day. With no row cap, a query with no WHERE returns 8 million rows, blows the context window, and still “worked”. With fetchmany(1000) and a 10s statement_timeout, the worst case is annoying, not catastrophic.

OWASP’s Top 10 for LLM Applications frames this as excessive agency: the model asks, code executes, and damage scales with the credential’s power. Cut the power.

Check +20 XP on first tryThe analytics assistant uses the application's DATABASE_URL, which can write. The prompt says ‘never run UPDATE or DELETE’. A pasted PDF asks it to mark an order as paid. Which fix actually holds the database?

Safe mechanics

  1. Measure. List what each tool really does in the traces: which tables it reads, which endpoints it calls, which amounts it moves. Minimal access comes from real use, not imagination.
  2. Define the minimal access for each tool from that list. Four views, not the schema. One endpoint, not the whole API.
  3. Create dedicated credentials per tool, with that scope, in a vault. Do not reuse DATABASE_URL.
  4. Propagate the end user’s identity to the tool and apply their permissions (RLS, API scopes).
  5. Add limits: maximum amount, maximum rows, maximum time, call rate.
  6. Test with malicious requests and confirm the infrastructure refuses them. Put those requests in the eval.
  7. Run the eval and confirm legitimate uses still work.

Trap: a cap that lives only in the prompt

Beginners often write “never access the payroll table” in the system prompt and call it done. The model obeys in manual tests. The first document that says “ignore previous rules” brings the table back. If the GRANT does not exist, the refusal shows up in the trace as a permission error, not as a model choice.

Another trap: a “temporary” broad credential, created on Friday for a spike, that stays in the production .env. Review permissions the way you review dependencies.

Check +20 XP on first tryThirty days of traces show the assistant only SELECTs from four analytics views. The current user can read the entire production schema. What is the next step?

Risks

  • Over-restriction. A rare legitimate use starts failing. The eval and traces show it; open a specific view instead of giving back the broad user.
  • Shared credentials again. Over time, someone reuses the broad credential “just to test”. Review permissions periodically, one tool at a time.
  • Identity lost along the way. If the end user does not reach the tool, their permissions do not apply. Test the full path, from the chat to the SET on the database.
  • A silent cap. fetchmany(1000) with no warning returns a quiet slice. Return a truncated: true field so the model does not treat a sample as a census.
Check +20 XP on first tryAfter you narrow access, 4% of queries fail because they asked for a table off the list. What do you do?

Challenge

A support assistant’s send_message(to, text) tool uses the company’s official WhatsApp account and can message any number. Which restrictions would you apply?

Show solution

The recipient stops being a model parameter: the tool only sends to the current conversation’s customer, whose number comes from the system. Cap messages per conversation and per minute. No outside links beyond a list of company domains. Messages outside the service window use only approved templates. The WhatsApp credential lives in the tool’s code, never in the prompt.

Check your understanding

Check +20 XP on first tryThe send_message(to, text) tool uses the company's WhatsApp account. The prompt says ‘only message the customer in this conversation’. The to parameter still comes from the model. Are the permissions narrow enough?

Check +20 XP on first tryThe lookup tool runs with the logged-in user's RLS, but the database role can still DROP TABLE. What is missing?

Check +20 XP on first tryA Pix scheduling tool (Brazil's instant payment system) is tied to the session user and has a R$500 cap in code. The model is tricked and asks to schedule R$50,000. What happens if the refactoring was done?

Quick answers

What is Narrow Tool Permissions?

Narrow Tool Permissions is an AI refactoring for LLM systems, in the Simplifying tools group. Give each tool only the access it needs for its use case.

What problem does Narrow Tool Permissions solve?

It treats Overprivileged Tool.

Which pattern does Narrow Tool Permissions lead to?

The Least-Privilege Tools design pattern.

Go further

Relations

Smells it treats 1
Target patterns 1
Part of the tracks 1
Search