← Back to Build Log
securityauthorizationpolicyzero-trustbest-practices

Authorization Patterns for AI Agents: From Allow-All to Zero Trust

A study published in early 2026 found that over 60% of production AI agent deployments had no meaningful access controls on tool use. The agents could call any tool, with any arguments, at any time. The developers building these systems were not careless. They were moving fast, and the framework they were using made adding tools easy and adding controls hard.

That ratio needs to flip. Adding a tool to an agent takes ten minutes. Recovering from an agent that deleted production data, exfiltrated a database, or sent a thousand spam emails takes much longer.

This post covers four authorization patterns in increasing order of rigor, with policy examples for each using Authensor.


Pattern 1: Allow-All (Do Not Ship This)

The allow-all pattern is what you get when you wire up an agent with no policy layer at all. Every tool call succeeds. Every argument is accepted. There is no audit trail.

This is not a viable production pattern. But it is worth naming explicitly because it is the default state of most framework integrations. If you have not added authorization, you are running allow-all.

// Allow-all: no policy, no controls, no audit trail
import OpenAI from "openai";

const agent = new OpenAI();
// Tools execute whatever the model decides.
// There is no interception point.

What can go wrong. The agent is one crafted input away from calling delete_database, send_email_to_all_users, or execute_shell("rm -rf /"). There is no policy to prevent it, no record that it happened, and no way to know it was coming.


Pattern 2: Allowlist (Minimum Viable Security)

The allowlist pattern is the first meaningful security improvement. You define a set of permitted tool names. Anything not on the list is blocked. Arguments are not inspected — only the tool name matters.

This is a significant improvement over allow-all, but it is still incomplete. An allowlisted tool with unrestricted arguments is a half-open door.

# authensor-policy.yaml
version: "1"
rules:
  - id: allow-read-tools
    action:
      - read_file
      - search_web
      - query_crm
    decision: ALLOW

  - id: deny-everything-else
    action: "*"
    decision: DENY
import { AuthensorClient } from "@authensor/sdk";

const client = new AuthensorClient({
  apiKey: process.env.AUTHENSOR_KEY,
  agentId: "agent-research",
});

// Before executing any tool call:
const result = await client.evaluate({
  action: toolName,
  resource: toolArgs.resource,
  context: { agentId: "agent-research" },
});

if (result.decision !== "ALLOW") {
  throw new Error(`Tool call blocked: ${result.reason}`);
}

When to use this. For low-risk internal tools where the primary goal is preventing accidental use of dangerous tools. Research agents that only need to read, search, and query. Not appropriate for agents that take write actions.

What it misses. It does not inspect arguments, so query_crm with SELECT * FROM customers WHERE 1=1 passes the same as query_crm with customer_id=12345. It does not rate-limit, so the agent can call an allowlisted tool ten thousand times. It does not require human review for elevated operations.


Pattern 3: RBAC with Approvals (Recommended for Most Teams)

Role-based access control with approval workflows is the right default for production agents. Each agent has a role. Roles define what actions the agent can take autonomously and what requires human review. Argument constraints narrow the attack surface. Rate limits cap the blast radius.

This pattern covers the majority of the OWASP Agentic Top 10 risks and satisfies EU AI Act Article 14 requirements for human oversight.

# authensor-policy.yaml
version: "1"
rules:
  # Finance agent: read operations are autonomous
  - id: finance-read-autonomous
    principal:
      match: "agent-finance-*"
    action:
      - read_transaction
      - read_account_summary
      - generate_report
    decision: ALLOW

  # Finance agent: write operations require human review
  - id: finance-write-review
    principal:
      match: "agent-finance-*"
    action:
      - create_transfer
      - update_payment_details
      - create_invoice
    decision: REVIEW
    meta:
      reviewer_group: "finance-ops"
      timeout_minutes: 30

  # Rate limit: cap all finance agent calls per hour
  - id: finance-rate-limit
    principal:
      match: "agent-finance-*"
    rateLimit:
      requests: 100
      window: "1h"
    decision: DENY
    meta:
      reason: "Rate limit exceeded"

  # Deny all other actions for finance agent
  - id: finance-default-deny
    principal:
      match: "agent-finance-*"
    action: "*"
    decision: DENY
import { AuthensorClient } from "@authensor/sdk";

const client = new AuthensorClient({
  apiKey: process.env.AUTHENSOR_KEY,
  agentId: "agent-finance-prod",
});

const result = await client.evaluate({
  action: "create_transfer",
  resource: `accounts/${transferRequest.fromAccount}`,
  context: {
    amount: transferRequest.amount,
    currency: transferRequest.currency,
    agentId: "agent-finance-prod",
  },
});

if (result.decision === "ALLOW") {
  await executeTransfer(transferRequest);
} else if (result.decision === "REVIEW") {
  // Agent pauses. Approver reviews and approves or rejects via dashboard.
  await waitForApproval(result.receiptId);
} else {
  throw new Error(`Transfer blocked: ${result.reason}`);
}

When to use this. This should be the default pattern for any agent taking write actions in production. Finance agents, agents that send external communications, agents that modify databases or configurations, agents with access to user data.

What you get on top of allowlists. Per-principal rules that apply differently to different agent identities. REVIEW decisions that route elevated actions to human approvers without halting the agent entirely. Rate limits that cap damage from runaway or compromised agents. Argument constraints that narrow what an allowlisted tool can do.


Pattern 4: Zero Trust (High-Security Environments)

Zero trust for AI agents means every tool call is verified, no implicit trust is granted based on network location or agent identity alone, every action requires an explicit decision, and the blast radius of a compromised agent is minimized through tight scoping and aggressive rate limits.

Zero trust is appropriate for agents in regulated industries, agents with access to sensitive data, and agents operating in environments where a compromise would have serious consequences.

# authensor-policy.yaml — zero trust configuration
version: "1"
settings:
  default_decision: DENY          # Anything not explicitly ALLOW or REVIEW is denied
  require_receipt_claim: true     # ALLOW decisions must be claimed before execution
  toctou_protection: true         # Re-evaluate at claim time, not just at decision time
  require_principal_binding: true # Keys must match declared agent identity

rules:
  # Explicit allowlist with argument constraints
  - id: db-read-scoped
    principal:
      match: "agent-data-analyst"
    action: query_database
    condition:
      resource:
        match: "^analytics/[a-z0-9_]+$"   # Only analytics schema
      args:
        operation:
          enum: ["SELECT"]                  # Read-only, enforced at policy layer
    rateLimit:
      requests: 50
      window: "1h"
    decision: ALLOW

  # Anything touching PII goes to security review
  - id: pii-access-review
    principal:
      match: "agent-data-analyst"
    action: query_database
    condition:
      resource:
        match: ".*(users|customers|personal_data).*"
    decision: REVIEW
    meta:
      reviewer_group: "security-team"
      require_multi_party: true     # Two approvers required
      approval_count: 2

  # Time-window restriction: only business hours
  - id: business-hours-only
    principal:
      match: "agent-data-analyst"
    condition:
      timeWindow:
        days: ["mon", "tue", "wed", "thu", "fri"]
        hours: { start: "08:00", end: "18:00" }
        timezone: "UTC"
    action: "*"
    decision: DENY
    meta:
      reason: "Operations restricted to business hours"

The TOCTOU protection deserves specific attention. When require_receipt_claim is enabled, an ALLOW decision is not enough — the agent must claim the receipt just before execution. At claim time, the control plane re-evaluates the intent against the current policy. If the policy changed between decision and execution, the updated rules apply. This closes the gap where an agent could stockpile approvals, wait for a policy change, and execute stale permits.

import { AuthensorClient } from "@authensor/sdk";

const client = new AuthensorClient({
  apiKey: process.env.AUTHENSOR_KEY,
  agentId: "agent-data-analyst",
  strictIdentity: true,
});

// Step 1: Evaluate the intent
const result = await client.evaluate({
  action: "query_database",
  resource: "analytics/monthly_revenue",
  context: { operation: "SELECT", agentId: "agent-data-analyst" },
});

if (result.decision !== "ALLOW") {
  throw new Error(`Blocked: ${result.reason}`);
}

// Step 2: Claim the receipt immediately before execution.
// This triggers TOCTOU re-evaluation. If policy changed, this throws.
await client.claimReceipt(result.receiptId);

// Step 3: Execute only after successful claim
await executeQuery(queryParams);

When to use this. Regulated industries (finance, healthcare, legal). Agents with access to PII or financial data. Multi-agent systems where a single compromised agent could affect others. Any deployment where the cost of a mistake is high.


Choosing the Right Pattern

| Pattern | Autonomous Write Actions | Human Oversight | Argument Inspection | Rate Limiting | Effort | |---------|------------------------|-----------------|--------------------|-----------|----| | Allow-All | Yes, unrestricted | None | No | No | None | | Allowlist | Tool-scoped only | None | No | Optional | Low | | RBAC + Approvals | Role-scoped, some require review | Yes, for elevated actions | Yes | Yes | Medium | | Zero Trust | All explicitly authorized | Yes, with multi-party option | Yes, with constraints | Yes, aggressive | High |

Most production agents should be running Pattern 3. Pattern 2 is appropriate for internal read-only tooling. Pattern 4 is appropriate when the business risk of a mistake is severe. Pattern 1 should not exist in production.

The policy format, policy engine, and SDK are all open source. Start with npx create-authensor to scaffold a project with a Pattern 3 configuration as the default. The full policy reference is at authensor.com/docs. If you find edge cases or want to contribute policy examples, the repo is at github.com/authensor/authensor.