Skip to content
GEOstack
permissions policy

allow / ask / block: a permission model for agents

Why AI agents need default-deny permissions with a third verb — ask — that firewalls and IAM never had, and what that looks like in code.

GEOstack · · 5 min read

3 verbs: allow · ask · block — and default-deny for the rest

The short answer

A permission model for AI agents needs three verbs, not two: allow (execute and log), ask (pause for human approval), and block (refuse, log the attempt) — with anything undeclared blocked by default. It’s a firewall ruleset with one addition: a verb for “probably fine, but a human decides,” which binary systems never had.

Why two verbs aren’t enough

Every permission system you already run is binary. A firewall rule accepts or drops the packet. An IAM policy allows or denies the API call. Binary works there because the caller is deterministic: the same service makes the same calls for the same reasons, so you can decide each case once, ahead of time.

An agent breaks that assumption. The same agent, same tools, same policy can decide to issue a $4 refund or a $4,000 one depending on what’s in its context window — including whatever a user (or an injected document) put there. As of mid-2026 this is no longer hypothetical budget-wise either: Uber blew through its entire 2026 agentic-tools budget by April and capped engineers at $1,500/month per tool; Microsoft cancelled most internal Claude Code licenses in May 2026 with heavy users reportedly at $500–$2,000/month. (The famous ~$500M monthly Claude bill remains an unnamed company and an unconfirmed figure, per an AI consultant’s account reported by Axios — cite it as a story, not data.)

With a binary model you’re forced to misclassify the middle. issue_refund is too dangerous to always-allow and too useful to always-deny. Teams resolve this by allowing it and hoping — which is how the middle of your action list becomes the top of your incident list. ask exists for exactly that middle: execution pauses, a human sees the actual parameters ($4,000, account, reason), and approves or denies this instance, not the category.

Default-deny, or the policy is decorative

The second load-bearing decision is what happens to actions you never mentioned. Firewall people settled this decades ago: the last rule is DROP, and everything above it is an explicit exception. AWS IAM made the same call — no policy attached means no access.

Agent policies must work the same way, for a sharper reason: the set of things an agent might try isn’t enumerable. Models hallucinate tool names. MCP servers add tools in a version bump. A prompt injection asks for something you never imagined granting. Under default-allow, every one of those is silently permitted until someone notices. Under default-deny, they’re all blocked on arrival and show up in the log as attempts — which is also your early-warning system for both injection and drift.

import { Arc } from "@geostack/arc"

const arc = new Arc({ agent: "support-bot", defaultDecision: "block" })

arc.defineActions({
  lookup_order:  { defaultDecision: "allow" },
  issue_refund:  { defaultDecision: "ask",
                   allowIf: { amountLte: 50_00 } },   // ≤$50 skips the human
  delete_account:{ defaultDecision: "block" },
})

const decision = await arc.check("issue_refund", { amount: 4000_00 })
// → { decision: "ask", ... } — held for approval; the loop pauses.
// Approved decisions come back ES256-signed; your app verifies the
// signature before executing, so "approved" can't be forged upstream.

Two details that matter more than the verbs. First, the decision is enforced and verifiable, not advisory — Arc signs each execution decision (ES256) and your application checks the signature before acting, so a compromised agent or a man-in-the-middle can’t manufacture an allow. Second, every decision — including blocks and denied asks — lands in a hash-chained audit log, so the record of what the agent tried is as tamper-evident as the record of what it did.

Where the analogies break

Pre-conceding the limits of the firewall/IAM comparison:

  • Firewalls match packets; agent policy matches intent expressed as parameters. amountLte: 5000 is enforceable. “Only refund when the customer is right” is not a policy, it’s a wish. Push everything you can into structured parameters; what you can’t structure goes to ask.
  • IAM principals don’t get tired; your approvers do. An ask verb is a budget drawn on human attention. If an action is approved ~100% of the time, tighten its parameters and move it to allow — the audit log tells you which ones.
  • Policy doesn’t fix bad parameters on allowed actions. If send_email is allowed unconstrained, injection picks the recipient. Allowlists and thresholds are part of the model, not an add-on.
  • This governs the action layer. Token spend belongs to a gateway or a cumulative budget (Arc does the latter); neither replaces the other.

Migrating an existing agent

Don’t write the policy from imagination. Run the agent in shadow mode for a week with everything set to allow-and-log, then read the log: actions that fired constantly and harmlessly become allow, the consequential ones become ask with thresholds, everything else stays undeclared — which, under default-deny, means blocked. You’ll typically end with fewer than a dozen declared actions, which is the point: a policy you can read in one screen is a policy someone will actually review.

FAQ

What’s the difference between allow/ask/block and AWS IAM for agents? IAM is binary (allow/deny) and evaluates a principal’s static entitlements. Agent policy adds ask — per-instance human approval with the actual parameters — because an agent’s intent varies per call in ways entitlements can’t capture. Use IAM to bound what the agent’s credentials can do; use allow/ask/block to govern what it does do.

Why default-deny instead of just blocking known-dangerous actions? Because the dangerous list is open-ended: hallucinated tools, new MCP tools after an update, injection-driven requests. A denylist only stops what you predicted. Default-deny means anything unanticipated fails closed and shows up in the audit log as an attempt.

Can the agent be prompted to bypass the policy? Not if enforcement is outside the model. Arc’s check runs at the action boundary, decisions are ES256-signed and verified by your application before execution, and the model never holds the keys. What a prompt can still do is steer parameters on allowed actions — constrain those with thresholds and allowlists.


Arc is the spend-and-action guardrail for AI agents — free Developer tier, no credit card: npm i @geostack/arc. Quickstart →

Written by the GEOstack team. We build Arc — an allow / ask / block guardrail for autonomous agents. Spot something off? Tell us.