AI Agent Authorization: The Complete Guide to Pre-Execution Guardrails

AI agent authorization is the runtime layer that decides what a specific agent is allowed to do, right now, before a tool call executes. A complete technical guide to pre-execution guardrails, the Open Agent Passport standard, and how to build a fail-closed authorization system for autonomous agents.

21 min read
by Uchi Uchibeke

TL;DR

  • AI agent authorization is the runtime decision layer that answers a single question: "Is this specific agent allowed to execute this specific tool call, right now, under this policy?"
  • It is not authentication, not alignment, not a sandbox, and not an exec approval. It is a distinct layer that sits between the model's intent and the tool's execution.
  • The correct enforcement point is a pre-execution hook — the moment after the model has chosen a tool but before any side-effect runs.
  • This guide covers the four-layer agent security model, why traditional IAM breaks for agents, the pre-execution guardrail pattern, the Open Agent Passport (OAP) standard, and how to ship an authorization layer in Python and TypeScript.

1. What is AI agent authorization?

Authentication answers "who are you?" Authorization answers "what are you allowed to do?" For human users, the industry has spent twenty-five years building this distinction into OAuth, SAML, OIDC, RBAC, ABAC, and IAM. For AI agents, that infrastructure does not exist yet — and the gap is not academic. An agent that can call exec, fs.write, http.post, or payments.refund without an authorization layer is a production liability the moment it ships.

AI agent authorization is the runtime decision layer that intercepts a tool call after the model has chosen it and before the tool actually runs. It loads the agent's identity document, evaluates a declarative policy against the tool name and arguments, and returns a structured allow-or-deny decision with a reason. If denied, the tool call never executes. If allowed, the decision is logged, signed, and the call proceeds.

The key word is runtime. Authorization is not a training objective, not a system prompt, not a code review checklist. It is a function that runs on every tool call, deterministically, in milliseconds, and produces a record you can show an auditor.

Why traditional IAM does not work

IAM was built for humans clicking through web apps and services calling each other with long-lived API keys. It assumes:

  1. Sessions, not per-action checks. A human logs in once, gets a token, and uses it for an hour.
  2. Stable identity, not delegated chains. The principal is a user or a service, not "an agent acting on behalf of a user who delegated to a planner that spawned a worker."
  3. Coarse-grained scopes, not per-argument decisions. repo:write is fine for a developer; for an agent it must become "may write to src/ but not .github/workflows/."
  4. Human-speed mistakes. A human accidentally runs one bad command. An agent runs three hundred bad commands in eight seconds.

Agents violate every one of those assumptions. They do not log in; they execute. They do not have stable identity; they have ephemeral instances spawned by orchestrators. They do not need coarse scopes; they need argument-level constraints. And they do not make human-speed mistakes; they make machine-speed mistakes that fan out across tool calls before any human can intervene.

That is why agent authorization is its own layer, with its own data model, its own enforcement point, and its own audit format. Not a re-skin of OAuth.


2. The four-layer agent security model

Every production agent system has four security layers, whether the team building it knows it or not. Naming them makes it obvious which layer your current stack is missing.

+---------------------------------------------------------------+
|  Layer 4: AUDIT                                               |
|  What did the agent actually do? Signed, tamper-evident log.  |
+---------------------------------------------------------------+
|  Layer 3: AUTHORIZATION   <-- this guide                      |
|  What is this agent allowed to do RIGHT NOW, on this call?    |
+---------------------------------------------------------------+
|  Layer 2: AUTHENTICATION                                      |
|  Is this really the agent it claims to be? (signature, DID)   |
+---------------------------------------------------------------+
|  Layer 1: IDENTITY                                            |
|  Who is the agent? Stable identifier, owner, version.         |
+---------------------------------------------------------------+

Layer 1 — Identity. Every agent needs a stable identifier that survives restarts, redeployments, and orchestration. Not a session token. Not a process ID. A document you can point at and say "this is Agent X." In OAP this is the passport's id field, typically a DID.

Layer 2 — Authentication. Given an identifier, prove the agent is the one bound to it. Cryptographic signature over a request, attestation from a trusted issuer, or in low-trust environments, a hosted lookup against a registry.

Layer 3 — Authorization. Given an authenticated identity, decide whether this specific tool call is permitted right now. This is where pre-execution guardrails live. It is the layer most teams skip, because exec approvals and sandboxes feel like they cover it. They do not.

Layer 4 — Audit. Every decision — allowed or denied — is recorded as a structured, signed event. Not a log line. A verifiable record that maps to compliance requirements like SOX, HIPAA, and GDPR Article 22 (automated decision-making).

This guide is about Layer 3. Layers 1, 2, and 4 are prerequisites and outputs.


3. Traditional IAM vs Agent IAM

The shape of the problem is different. Side by side:

Dimension Traditional IAM (humans) Agent IAM
Principal User or service Agent instance with delegation chain
Granularity Per-session, per-resource Per-tool-call, per-argument
Latency budget Hundreds of ms (login flow) Tens of ms (in the tool-call hot path)
Identity lifetime Days to years Seconds to hours
Decision input Role + resource Tool name + tool arguments + agent context + caps
Failure mode One bad click Hundreds of bad calls fanned out in seconds
Delegation OAuth scopes Multi-hop on-behalf-of with cap reduction
Bypass surface Stolen credential Prompt injection, jailbreak, tool hallucination
Audit unit Login + resource access Every tool call with full input
Update frequency Quarterly access reviews Continuous; kill switch must be immediate

The mismatch is not subtle. RBAC assumes a human who is slow and forgetful. Agent IAM has to assume a process that is fast, deterministic, and capable of fanning out across hundreds of tool calls before any human notices. The enforcement point has to move from "at login" to "at every tool call," and the data model has to move from "roles and resources" to "capabilities, limits, and contextual constraints on tool arguments."


4. Pre-execution vs post-hoc authorization

There are exactly four places you can enforce policy on an agent action. Three of them are wrong.

Approach When it runs Problem
Policy in the system prompt Inside the model Bypassable by prompt injection. Probabilistic. No audit.
Exec approvals On a subset of tools Only covers shell. Misses HTTP, file, MCP, payments. Not declarative.
Sandbox / container isolation At the OS boundary Knows syscalls, not intent. Can't tell rm tmp.log from rm -rf /.
Pre-execution hook Between model and tool Correct. Declarative. Deterministic. Auditable.

A pre-execution hook is the only enforcement point that has all the information it needs (tool name, full arguments, agent identity, delegation chain) and runs before any side-effect happens. For the full argument against post-hoc enforcement, see pre-action vs post-hoc AI guardrails. This is the runtime enforcement gap that most agent stacks have today: the model emits a tool call, and the framework just runs it. There is no decision layer in between.

Closing that gap is the entire job of an agent authorization system.


5. The pre-execution guardrail pattern

Here is what happens, in order, when a guardrail is wired into an agent runtime. This is the canonical request flow.

+---------+        +-----------+        +------------+        +--------+
|  Model  | -----> |  Hook     | -----> | Authorizer | -----> |  Tool  |
| (LLM)   |  tool  | (intercept|  ctx   | (eval pol) |  ok    | (exec) |
+---------+  call  +-----------+        +------------+        +--------+
                        |                     |                    |
                        |                     v                    |
                        |               +-----------+              |
                        |               | Passport  |              |
                        |               | + Policy  |              |
                        |               +-----------+              |
                        |                     |                    |
                        |                     v                    |
                        |               +-----------+              |
                        +-------------> | Audit Log |<-------------+
                          deny event    |  (signed) |   allow event
                                        +-----------+

Step by step:

  1. The model emits a tool call. { toolName: "fs.write", toolInput: { path: "src/main.py", content: "..." } }
  2. The framework hook fires before the tool dispatcher runs. The hook receives { toolName, toolInput, agentId, context }.
  3. The authorizer loads the passport for agentId. This is either a local JSON file (offline mode) or an HTTP fetch from a registry, cached.
  4. Capability check. Is fs.write in the passport's capabilities array? If not, deny.
  5. Limit check. Has this agent exceeded its rate limit (e.g., 100 file writes per hour)? Its amount limit (e.g., max file size 1 MB)? If yes, deny.
  6. Context check. Does toolInput.path match the allowed path glob (e.g., src/)? Is the command in the allow-list? Is the recipient on the approved domain list? If no, deny.
  7. Decision returned. A structured object: { allow: true } or { allow: false, reason: "path_outside_allowed_glob", policy: "data.file.write.v1" }.
  8. Audit event written. Whether allowed or denied, a signed record of the decision is appended to the audit log.
  9. Tool runs, or is blocked. On allow, the framework dispatches the tool. On deny, the framework returns the reason to the model so it can plan around the constraint.

That is the entire pattern. Every detail in the rest of this guide is an elaboration of one of these nine steps.


6. Components of an authorization system

A complete agent authorization system has seven components. If you are building your own, you need all seven. If you are evaluating a vendor or open-source library, ask which of these they ship.

1. Identity document. A passport or credential file that names the agent, its owner, its version, and the keys used to authenticate it. Stable across restarts. Verifiable.

2. Capability declaration. The list of tools this agent is allowed to call at all. A capability is the coarse gate; if a tool is not on this list, the agent cannot use it under any circumstances. ["fs.read", "fs.write", "http.get"] is a capability set.

3. Limits. Quantitative bounds on capabilities. Rate (calls per minute), amount (bytes per call, dollars per refund), and scope (allowed paths, allowed domains, allowed recipients). Limits are what turn fs.write from "may write any file" into "may write up to 100 files per hour, each up to 1 MB, only under src/."

4. Policy evaluator. The deterministic function that takes (passport, toolName, toolInput, context) and returns { allow, reason }. This must be pure, fast (sub-100 ms), and free of model inference. No LLM in the enforcement path.

5. Decision format. A stable, structured response. Allow-or-deny plus a machine-readable reason code, the policy ID that produced the decision, and the passport version. Frameworks render the reason back to the model so it can adapt its plan.

6. Kill switch. A global revocation mechanism. When the security team marks a passport as suspended, every subsequent decision returns deny within the cache TTL (typically seconds). No code change, no redeploy.

7. Audit log. Every decision written as an Ed25519-signed event with the full input, the decision, the reason, and a hash chain to the previous event. Tamper-evident. Exportable to SIEM. Mappable to compliance frameworks.

Skip any one of these and the system is incomplete. A capability list with no limits is a coarse gate. Limits with no kill switch cannot respond to incidents. Decisions with no audit log cannot pass an audit. All seven, or none.


7. The Open Agent Passport (OAP) standard

The data model for an agent authorization system needs to be standard, or every framework reinvents it incompatibly. The Open Agent Passport (OAP) is the open spec for that data model: a JSON document, DID-based identity, declarative capabilities and limits, signed by the issuer, verifiable by any conforming implementation. Apache 2.0, no central registry required.

A minimal OAP passport:

{
  "spec_version": "oap/1.0",
  "id": "did:web:agents.example.com:planner-prod",
  "name": "Planner (prod)",
  "owner": "example.com",
  "capabilities": ["fs.read", "fs.write", "http.get", "system.command.execute"],
  "limits": {
    "fs.write": { "max_bytes": 1048576, "rate_per_hour": 100, "allowed_paths": ["src/**", "tests/**"] },
    "http.get": { "allowed_domains": ["api.example.com", "docs.python.org"] },
    "system.command.execute": { "allowed_commands": ["ls", "cat", "git status", "pytest"] }
  },
  "assurance": "L2",
  "issued_at": "2026-04-01T00:00:00Z",
  "expires_at": "2026-07-01T00:00:00Z",
  "signature": "ed25519:..."
}

The spec lives at github.com/aporthq/aport-spec. Every example in this guide is OAP-conformant, but the pattern works with any structured policy format. The standard is the contract; the evaluator is the implementation.


8. Building an authorization layer: three approaches

There are three honest ways to ship agent authorization. Each has tradeoffs.

Approach 1 — Build it yourself. Write a custom hook in your framework's middleware slot, write a custom policy evaluator, store passports in your config repo. Pros: maximum control, no dependency. Cons: you now own the spec, the evaluator, the audit format, the kill switch, and the compliance mapping. This is at least one engineer-quarter of work, plus ongoing maintenance, and you will get the audit format wrong on the first three tries.

Approach 2 — Use a library. Adopt a framework-native integration like DeerFlow's GuardrailProvider protocol or OpenClaw's plugin hook path. Pros: framework-native, fast to wire up, you keep your existing policy logic. Cons: you are coupled to one framework's interface; portability across stacks is limited.

Approach 3 — Use a standard plus a provider. Adopt OAP as the data model and pick any conforming evaluator (local CLI, hosted API, or self-hosted). Pros: portable across frameworks, audit format is fixed by the spec, kill switch works across every agent in your fleet. Cons: you have to learn the spec.

The right answer for a single hobby project is Approach 1. The right answer for a team shipping agents to production is Approach 3. Approach 2 is the migration path between them.


9. Framework integration examples

The pattern is the same in every framework: the framework exposes a hook that fires before tool execution; you wire an authorizer into that hook. The differences are syntactic.

Python + DeerFlow

DeerFlow exposes a GuardrailProvider protocol and reads providers from config.yaml. First install and run the setup wizard:

pip install aport-agent-guardrails
aport setup --framework deerflow

Then add the guardrails: section to your DeerFlow config.yaml:

guardrails:
  enabled: true
  fail_closed: true
  passport: ~/.aport/deerflow/aport/passport.json
  provider:
    use: aport_guardrails.providers.generic:OAPGuardrailProvider

Once configured, every tool call from every DeerFlow agent — planner, researcher, coder — passes through the pre-execution hook before running. See DeerFlow PR #1240 for the full integration.

Python + LangChain

LangChain exposes BaseCallbackHandler.on_tool_start. APort ships APortCallback, which implements that protocol. Install and wire it in:

pip install aport-agent-guardrails-langchain
aport-langchain setup
from langchain.agents import initialize_agent
from aport_guardrails_langchain import APortCallback

agent = initialize_agent(
    tools=tools,
    llm=llm,
    callbacks=[APortCallback()]
)

Config is read from ~/.aport/langchain/ or .aport/config.yaml. Every tool call now goes through the authorizer. Denials raise an exception that LangChain surfaces back to the model as a tool error, so the planner can adapt.

TypeScript + OpenClaw

Today, the public OpenClaw integration is plugin-based. APort installs the openclaw-aport plugin, writes the plugin config, and evaluates every tool call in before_tool_call:

npx @aporthq/aport-agent-guardrails openclaw
openclaw gateway start --config ~/.openclaw/config.yaml

That path works on current public OpenClaw with no core patch. OpenClaw's built-in sandboxing and tool policy remain useful; APort adds per-agent authorization, limits, kill switch, and audit at the tool boundary.

Cursor

Cursor exposes config-driven hooks (~/.cursor/hooks.json). The APort installer writes those hook entries for you and runs the passport wizard:

npx @aporthq/aport-agent-guardrails cursor

Cursor invokes bin/aport-cursor-hook.sh with the proposed command as JSON on stdin. The script calls the APort evaluator and returns permission: allow|deny; exit code 2 blocks. The full evaluator runs locally — no network call in the hot path. For Claude Code, use the dedicated installer npx @aporthq/aport-agent-guardrails claude-code (it writes ~/.claude/settings.json with the correct hookSpecificOutput.permissionDecision output format).

The same pattern works for Claude Code, OpenAI's Assistants API, CrewAI, AutoGen, and any framework with a pre-tool hook. If a framework does not have one, the integration is to wrap the tool dispatcher itself.


10. Policy packs

Hand-writing policy logic for every tool is how policies get inconsistent and stale. Policy packs are reusable, versioned policies for common tool categories. You reference them by ID; the evaluator knows how to interpret them.

Pack ID What it gates Typical limits
system.command.execute.v1 Shell commands allowed_commands, denied_args, rate
data.file.write.v1 File writes allowed_paths, max_bytes, denied_extensions
data.file.read.v1 File reads allowed_paths, denied_paths
web.fetch.v1 HTTP requests allowed_domains, methods, max_response_bytes
mcp.tool.execute.v1 MCP tool invocations allowed_servers, allowed_tools
finance.payment.refund.v1 Refund operations (vertical) max_amount, currency, allowed_reasons

Vertical packs like finance.payment.refund.v1 are how you carry domain rules into the authorization layer without rebuilding the evaluator. The pack defines the schema and the constraints; your passport supplies the values.

Versioning is non-negotiable. system.command.execute.v1 is frozen forever once published. If you need new fields, ship v2 and let teams migrate. Otherwise every passport becomes a deployment risk.


11. Evaluation modes

The evaluator can run in three modes. Pick based on latency budget and trust model.

Local (offline). The evaluator is a binary or a library that runs in-process. Passports are JSON files on disk. No network call. Median latency in the published APort testbed is ~40 ms (bash evaluator). Use this for IDE assistants (Cursor, Claude Code), CI runners, and air-gapped environments.

API (hosted). The evaluator is an HTTP service. Passports are stored centrally; updates propagate to all agents through cache invalidation. Median latency in the published APort testbed is ~53 ms. Use this for fleets where you need centralized policy management and a global kill switch.

Hybrid. Local cache, remote source of truth. The agent fetches and verifies the passport once, caches it for a short TTL (30–300 seconds), and evaluates locally during the cache window. Kill switches propagate within one TTL. Use this for production agent fleets at scale.

All three modes return the same decision format. The choice is operational, not architectural.


12. Fail-closed vs fail-open

The default for any authorization system must be fail-closed: if the evaluator cannot reach a decision — passport missing, policy unparseable, signature invalid, network down — the answer is deny. Anything else is a bypass surface. An attacker who can break your evaluator is not the worst case; the worst case is an attacker who can quietly break your evaluator and let every tool call through with no record.

Fail-closed is the only safe default. There are exactly two scenarios where fail-open is defensible:

  1. Read-only tools in dev environments. A local IDE assistant whose only capability is fs.read on the user's own machine. Failing closed here just frustrates the developer. Failing open is a developer-experience choice with no production blast radius.
  2. Explicit operator override during incident response. A break-glass mode that flips an entire fleet to fail-open for a fixed window, with a loud audit event and an automatic revert. Used when the authorizer itself is the incident.

Every other case is fail-closed. Make the deny path fast and the deny reason readable so the model can adapt.


13. Audit and compliance

A decision that is not recorded did not happen. The audit log is what turns an authorization layer into a compliance artifact.

Every decision — allow and deny — is written as a structured event:

{
  "ts": "2026-04-08T14:32:11.482Z",
  "agent_id": "did:web:example.com:planner",
  "tool": "fs.write",
  "input_hash": "sha256:c3a1...",
  "decision": "deny",
  "reason": "path_outside_allowed_glob",
  "policy": "data.file.write.v1",
  "passport_version": 7,
  "prev_hash": "sha256:b21f...",
  "signature": "ed25519:..."
}

Three properties matter. Signed with Ed25519, so an auditor can verify the log was not edited after the fact. Hash-chained to the previous event, so deletion of any single event breaks the chain. Structured, so it maps cleanly to the controls in SOX (segregation of duties), HIPAA (access logging), GDPR Article 22 (automated decisions), and SOC 2 (logical access). When the auditor asks "what did your AI agent do, and who said it could," the answer is a query, not an apology.


14. Common pitfalls

The same five mistakes show up in every team's first pass at agent authorization. They are easy to avoid once named.

Pitfall 1 — Authorization in the prompt. "You are a helpful assistant. Do not run destructive commands." This is a wish, not an authorization layer. It is bypassable by prompt injection, by the model's own creativity, and by any tool the model decides to call without thinking. Authorization belongs in code, not in tokens.

Pitfall 2 — Authorization after execution. Logging a tool call after it ran is observability, not authorization. By the time the log line is written, the file is deleted, the payment is sent, the email is in the recipient's inbox. Pre-execution or it does not count.

Pitfall 3 — Per-session checks only. Checking the agent's identity at session start and trusting every subsequent tool call is OAuth thinking. Agents fan out hundreds of tool calls per session. Every call needs its own decision.

Pitfall 4 — Hardcoded policies. Policies baked into application code cannot be updated without a deploy. Your incident response window is now however long it takes to ship a release. Policies must be data, loaded at runtime, with a kill switch that takes effect within seconds.

Pitfall 5 — Silent allow on error. A try/except around the evaluator that returns "allow" on any exception is the most dangerous bug in this entire stack. It looks like resilience and behaves like a backdoor. Fail-closed, always, with a loud error.


15. FAQ

What is the difference between authentication and authorization for AI agents?
Authentication proves the agent is who it claims to be. Authorization decides what that authenticated agent is allowed to do, on a per-tool-call basis. Authentication is a one-time check; authorization runs on every action.

Is this the same as exec approvals?
No. Exec approvals only cover shell commands and only ask "should I run this command?" Pre-execution authorization covers every tool — file, HTTP, MCP, payments, sub-agent delegation — and evaluates a declarative policy with full argument context. Exec approvals are a subset of one policy pack.

Can prompt injection bypass it?
No, because the enforcement point is outside the model. The model can be convinced to attempt any tool call it wants; the authorizer evaluates the actual tool call against the actual policy, regardless of what the model was told. If the policy says "no writes outside src/," prompt injection cannot widen that scope, because the model is not the decision-maker.

What about latency?
Local evaluators (bash) run at ~40 ms median in the published APort testbed. Hosted evaluators run at ~53 ms median. Compared to a single LLM token, this is noise. Compared to a single tool call's actual work (file write, HTTP request), it is a rounding error. Latency is not a real objection.

How do I suspend an agent globally?
Mark its passport as suspended in the registry. Every evaluator that fetches the passport — directly or through cache invalidation — returns deny on the next call. In hybrid mode, propagation is bounded by your cache TTL, typically 30 to 300 seconds. No code change. No redeploy.

Does this replace my WAF, IAM, or RBAC?
No. Those layers protect humans and services. Agent authorization is a parallel layer for agents. Your WAF still inspects HTTP. Your IAM still gates human access. Your RBAC still governs service-to-service calls. Agent authorization adds the missing layer that none of those were designed for: the per-tool-call decision for autonomous agents.


16. Conclusion and next steps

AI agent authorization is not a feature you add at the end of a project. It is the layer that makes the difference between an agent you can ship to production and a research demo that occasionally deletes a database. The pattern is settled: a pre-execution hook, a declarative policy, a deterministic evaluator, a signed audit log, and a kill switch. The standard for the data model is the Open Agent Passport. The integration in every major framework is a config block.

If you are building an agent today, the cheapest moment to add authorization is now, before the second tool gets wired in. The most expensive moment is after the first incident.

Where to go next:

The question is not whether your agents need an authorization layer. They already do. The question is whether you write it down before something forces you to.

Frequently Asked Questions

Common questions about this topic.

What is the difference between authentication and authorization for AI agents?

Authentication proves the agent is who it claims to be. Authorization decides what that authenticated agent is allowed to do, on a per-tool-call basis. Authentication is a one-time check; authorization runs on every action.

Is this the same as exec approvals?

No. Exec approvals only cover shell commands and only ask 'should I run this command?' Pre-execution authorization covers every tool (file, HTTP, MCP, payments, sub-agent delegation) and evaluates a declarative policy with full argument context. Exec approvals are a subset of one policy pack.

Can prompt injection bypass it?

No, because the enforcement point is outside the model. The model can be convinced to attempt any tool call it wants; the authorizer evaluates the actual tool call against the actual policy, regardless of what the model was told. If the policy says 'no writes outside src/', prompt injection cannot widen that scope, because the model is not the decision-maker.

What about latency?

Local evaluators (bash) run at ~40ms median in the published APort testbed. Hosted evaluators run at ~53ms median. Compared to a single LLM token, this is noise. Compared to a single tool call's actual work (file write, HTTP request), it is a rounding error. Latency is not a real objection.

How do I suspend an agent globally?

Mark its passport as suspended in the registry. Every evaluator that fetches the passport returns deny on the next call. In hybrid mode, propagation is bounded by your cache TTL, typically 30 to 300 seconds. No code change. No redeploy.

Does this replace my WAF, IAM, or RBAC?

No. Those layers protect humans and services. Agent authorization is a parallel layer for agents. Your WAF still inspects HTTP, your IAM still gates human access, your RBAC still governs service-to-service calls. Agent authorization adds the missing layer that none of those were designed for: the per-tool-call decision for autonomous agents.