Skip to main content
Back to blog

NOVEMBER 14, 2026

AI Agent Permissions: Rules Belong in Code, Not Prompts

An AI agent inside your product should never decide what it is allowed to do. Here is where each permission rule has to live, from the tool list down to the database, and how to sort actions into ones that run freely and ones that wait for a person.

By Umar Abdullah · AI Product Engineering

Light cover reading "The prompt is not a permission system" beside a stack of five layers for AI agent permissions: tool list, tool code, user identity, database and approval
November 14, 202610 min read

Short answer

An AI agent's permissions belong in your code and your database, not in its prompt. The prompt tells the agent what you would like it to do. Only the tool layer, the server and the database can decide what it is able to do. Give the agent few, narrow tools, run every tool call as the signed-in user, check each call on the server as if it came from a stranger, and make irreversible actions wait for a person.

Why can't the prompt be the permission system?

Because the model reads your rules and an attacker's text in the same way. A line in the system prompt that says "never refund more than the order total" is one more piece of text. So is a sentence hidden in a support ticket, a web page or a PDF that the agent is asked to read.

The 2025 edition of OWASP's guidance on prompt injection is direct about this. It says that, given how models work, "it is unclear if there are fool-proof methods of prevention for prompt injection." It also notes the injected text does not have to be visible to a person, as long as the model parses it.

Attack is not the only cause. OWASP's entry on excessive agency describes damaging actions performed "in response to unexpected, ambiguous or manipulated outputs from an LLM, regardless of what is causing the LLM to malfunction." A confused model and a hijacked model do the same harm if the tool lets them.

So the design question is not "how do we make the model obey?" It is "what is the worst this tool call can do if the model is wrong?" The answer has to be acceptable before you ship.

The same mistake we find in AI-built apps, one layer up

We have seen this shape before. In the code security audits we delivered over the last year, 35+ in total and 24 of them mostly AI-built, 18 had broken or missing authorization and 16 had database rules open to every logged-in user. These overlapped heavily and had one cause: the access check lived in the UI. The dashboard only showed your records, so the demo looked right, while the API behind it answered any logged-in user who changed an ID.

A rule in a prompt is that mistake moved up a layer. The agent only talks about your own orders, so the demo looks right. The tool behind it will still fetch any order ID it is handed.

This is why two of the three decisions our engineers keep on every project are where each rule lives and what runs on the server. Business logic gets one home, not copies spread across the client, the server and the prompts. Anything that touches money, permissions or user data stays on the server and is reviewed. An agent does not change that rule. It adds one more client that must not be trusted.

Where should each rule live?

Think of an agent as five enforcement layers, with the prompt sitting outside them. Each layer can stop a bad action, and each lower layer still holds when the one above it fails.

Layer The rule it enforces Who can change it
Tool list What the agent can attempt at all Your engineers, in code
Tool code Which arguments are valid: ranges, formats, allowed values Your engineers, in code
User identity Whose data this call may touch Your auth system
Database Which rows and operations this identity may use Your schema and policies
Approval step Whether this specific action goes ahead A person, at the time
Prompt Tone, steps, when to ask, what to prefer Anyone whose text the model reads

Only the last row can be rewritten by a stranger. Keep it for behaviour, and never let it be the only thing between a user and a damaging action.

Diagram of the five enforcement layers for AI agent permissions: a tool call passes through the tool list, tool code, user identity, database rules and an approval step, while the prompt sits outside as guidance that cannot block anything
Diagram of the five enforcement layers for AI agent permissions: a tool call passes through the tool list, tool code, user identity, database rules and an approval step, while the prompt sits outside as guidance that cannot block anything

Tool list. OWASP's first advice is to limit tools to the minimum necessary, and to avoid open-ended ones such as "run a shell command" or "fetch a URL" in favour of narrow tools that do one thing. Its example is an assistant that summarises a mailbox through a plugin that can also send mail. One hostile email later, the assistant forwards sensitive mail to an attacker. With a read-only tool there is nothing to send with.

Tool code. Treat tool arguments like any request body from the internet. The model wrote them, and the model may have been steered. Validate types and ranges, and recompute anything that matters from your own records. A refund tool takes an order ID and looks up the amount. It does not accept an amount. The Model Context Protocol specification makes this a requirement for tool servers: validate all tool inputs, implement proper access controls, rate limit tool invocations and sanitize tool outputs.

User identity. OWASP calls this executing in the user's context. The agent acts for one signed-in user, so every downstream call carries that user's identity and scope. The failure it warns about is a tool that reaches downstream systems "with a generic high-privileged identity." If the agent's service account can read every tenant, one bad tool call can too.

Database. OWASP's example here is a tool meant to read data that connects with an identity holding SELECT, UPDATE, INSERT and DELETE. Give the agent's connection only the tables and operations its tools need, and keep row-level rules on, so a mistake in tool code still cannot cross a tenant boundary. OWASP's name for the principle is complete mediation: "Implement authorization in downstream systems rather than relying on an LLM to decide if an action is allowed or not."

Approval step. Some actions should not run on a model's judgment at all. Those wait for a person.

Which actions can run alone, and which wait for a person?

Sort every tool by what happens if it runs when it should not have.

Action class Examples Control
Read, inside the user's scope Look up my orders, search our docs Run. Enforce scope on the server. Log it.
Reversible write Draft a reply, add a tag, save a note Run. Keep an undo and an audit trail. Rate limit.
Write that leaves the system Send an email, post a message, call a partner API Show the exact content and wait for approval.
Irreversible or financial Refund, delete, change a role, cancel a plan Approval every time, plus server-side limits.
Open-ended Run code, fetch any URL, query any table Avoid. Replace with narrow tools, or isolate it.

Decide by action and by argument, not by agent. A refund under a small limit and a refund of a full annual contract are different risks, even though they are one tool.

Agent frameworks now support this in code. In the OpenAI Agents SDK, a tool can be marked as needing approval, either always or through a function that inspects the parsed arguments for each call. The run pauses until a person approves or rejects it. The documentation notes two details worth copying whatever stack you use. Function-based approval rules "fail closed" when the arguments cannot be safely inspected, so a malformed call goes to a person instead of slipping through. And for the SDK's local shell and file-edit tools, approval is opt-in and off by default, so the safe setting is something you have to choose.

Ship faster with senior engineers

Direct collaboration, AI-augmented delivery, and no agency markup.

Get in touch

What should an approval step look like?

An approval that people click without reading is not a control. Three things keep it useful.

  • Show the real action. The MCP specification says clients should "show tool inputs to the user before calling the server, to avoid malicious or accidental data exfiltration". Show the recipient, the amount and the full text, not a summary written by the model.
  • Ask rarely. If every read asks for permission, people approve everything. Reserve prompts for the bottom rows of the table above.
  • Be careful with "always allow". A remembered approval is a standing permission. Scope it to one tool and one kind of argument, and let it expire.
  • The MCP specification puts the baseline in one sentence: there "SHOULD always be a human in the loop with the ability to deny tool invocations."

    What do guardrails and filters still do?

    They are useful, as long as you know which job they do. A guardrail is usually a second, cheaper model or classifier that checks input or output. It can stop off-topic requests, catch obvious abuse and save cost.

    It is not a permission check, and timing matters. The OpenAI Agents SDK guardrails documentation explains that input guardrails run in parallel with the agent by default, so if one trips, "the agent may have already consumed tokens and executed tools before being cancelled." A blocking mode exists for that reason. The same page notes that input guardrails run only for the first agent in a chain, while tool guardrails run on every guarded function-tool call and do not cover hosted tools or the built-in shell and file-edit tools. If a check has to hold on every action, put it in the tool's own code.

    There is also research on defeating prompt injection by design, such as CaMeL from Google, Google DeepMind and ETH Zurich. Its authors publish the code as a research artifact, not a product. It is worth following. It does not remove the need for the layers above.

    What does this look like in a real product?

    The plain version is one backend that owns every rule, with the model as one more caller. When we built RozmeriGPT, a web dashboard and a Chrome extension shared a single backend. The OpenAI integration, with its usage monitoring and cost controls, lived in that backend, and prompt and usage controls were centralised across both clients. An agent is a third client of that same backend. If it has to pass through the same checks as the web app, most of the work is already done.

    The reverse also holds. If your API today trusts its own frontend, adding an agent turns a quiet weakness into a loud one, because the agent will send requests no screen of yours would ever send.

    A checklist before you ship an agent

  • List every tool. Remove any the feature does not need. Split broad tools into narrow ones.
  • For each tool, write down the worst outcome if it is called with hostile arguments. If the answer is "another customer's data" or "money moves", fix the tool, not the prompt.
  • Make every tool call run as the signed-in user. No shared admin key behind the agent.
  • Recompute prices, totals, owners and roles on the server. Never accept them as arguments.
  • Give the agent's database identity only the tables and operations its tools use, with row-level rules on.
  • Mark irreversible and outbound actions as approval-required, and show the real arguments in the approval.
  • Set limits: steps per run, calls per minute, spend per user per day.
  • Log every tool call with user, arguments, decision and result.
  • Test without the model. Call each tool as the wrong user and with tampered IDs. Then feed the agent documents that contain hostile instructions and check that the worst result is a refused call.
  • Delete every sentence in the prompt that begins with "never" and ask whether the product is still safe. If it is not, a rule is living in the wrong place.
  • When we take on AI app development, we build the evaluation set before the first prompt is released and model token spend before launch. Agent permissions belong in that same early design step. If the agent is already live and nobody has tried step 9, our AI code security audit covers prompt injection and API abuse paths along with the rest of the code.

    Sources: OWASP Top 10 for LLM Applications, 2025 edition, LLM01 Prompt Injection and LLM06 Excessive Agency; Model Context Protocol specification 2026-07-28, Tools; OpenAI Agents SDK documentation, Human-in-the-loop and Guardrails; CaMeL research code. Entalogics figures are from our own audit reports and project records.

    COMMON QUESTIONS

    Straight answers.

    Eight questions we get on every first call. If yours isn't here, it'll be the first thing we cover.

    No. A system prompt shapes how the agent behaves on a normal day, but it is text the model weighs against all the other text it reads. OWASP states it is unclear whether fool-proof prevention of prompt injection exists. Treat the prompt as guidance and put every rule that matters in code the model cannot change.
    It is the OWASP name for an agent that can do more damage than its job requires. OWASP lists three root causes: too much functionality (tools it does not need), too many permissions (tools that connect with broad access) and too much autonomy (high-impact actions with no independent check).
    For anything done on behalf of a user, use that user's identity and scope. If the agent connects with one powerful shared account, a single manipulated request can reach every customer's data. Running in the user's context means the worst case is limited to what that user could already do.
    Actions that cannot be undone or that leave your system: sending messages, moving money, deleting records, changing roles, publishing content. Reads inside the user's own scope and drafts that a person will review can usually run without a prompt. Decide per action and per argument, not per agent.
    They reduce noise and catch obvious misuse, which is worth having. They are not an access control. A filter is another model making a judgment, so it can be wrong. Use filters on top of server-side permission checks, never in place of them.
    No. The Model Context Protocol is a way to connect tools to models. Its specification still requires servers to validate inputs, implement access controls and rate limit tool calls, and says a human should be able to deny tool invocations. The protocol carries the call; your server still decides whether it is allowed.
    Skip the model and call each tool directly as the wrong user, with another customer's IDs and with out-of-range values. Every call should fail on the server. Then run the agent against documents and pages that contain hostile instructions and confirm the worst result is a refused tool call.
    Every tool call: who the user was, which tool, the arguments, whether a policy or a person allowed it, and the result. OWASP notes that logging and rate limiting do not prevent a bad action, but they limit the damage and make it possible to find out what happened.

    Ready to Build Something Amazing?

    Let's discuss your project and see how we can help you achieve your goals with quality software at fair pricing.