Runtime authorization for AI agents is a decision made on every tool call, after the agent proposes it and before it runs: may this agent perform this action on this resource, here, now? This guide covers what that decision needs, where to enforce it, and a checklist for putting your first agent under policy with Cirvix.
PERMIT / HOLD / DENY: one verdict per call
Default deny: no rule, no execution
Forbid wins: permits can't override
Apache-2.0: runs locally
Definition
What runtime authorization means for an agent.
Capability is not authority. An agent that can call a tool has not been authorized to call it with these arguments.
An AI agent turns text into consequences: it reads files, queries databases and calls MCP tools and APIs, usually with the credentials of whoever launched it. Most stacks decide what an agent may touch once, at configuration time. After that, every individual call is trusted by default.
Runtime authorization moves the decision to the moment of action. The agent proposes a structured request (who is asking, which action, which resource, in what context), a deterministic policy engine evaluates it, and the tool runs only if the verdict allows it. With Cirvix, every governed agent action routed through Cirvix is evaluated before execution, and the decision can be recorded in a SHA-256 hash-chained audit log.
| Control | When it decides | What it sees | What it can't do |
|---|---|---|---|
| Authentication / IAM | When a credential is issued | Identity and scopes | Judge a single call's resource or context |
| Prompt guardrails | When text goes in or out of the model | Text | Stop a tool call the model has already chosen |
| Post-hoc monitoring | After execution | Logs | Undo a side effect |
| Runtime authorization | Before each governed call executes | Agent, action, canonical resource, context | Evaluate calls that bypass its enforcement point |
These layers compose. Keep IAM and prompt defenses, and add runtime authorization where side effects happen. The category overview is on the AI-agent security page.
Why runtime
Why the decision has to happen at runtime.
Three properties of agents break configuration-time permissions.
01
Agent inputs are untrusted by design
Pull request titles, web pages, documents and tool results can all carry instructions the model will follow. You cannot vet the inputs, so you have to evaluate the actions they produce.
02
The same tool means different things in different contexts
Writing a file in a scratch workspace is routine. Applying a manifest in production is not. A static grant cannot tell the two apart; a rule with a when condition on environment can.
03
Harm comes from sequences
Reading a credential and making an outbound request are each ordinary. Together they are exfiltration. Cirvix tracks session.touchedSecret, so the starter rule deny-external-egress-after-secret refuses external egress for the rest of a session that has read secret-shaped material.
Anatomy
Anatomy of one decision.
Request in, canonical resource, ordered rules, exactly one verdict, an explanation out.
Each request carries an agent name, an action such as fs.read or k8s.apply, a resource, and context (environment, whether the path is inside the workspace, whether the session has touched a secret, which MCP server and tool). Before any rule is compared, the resource is canonicalized: ./x/../.env, .env and an absolute path to the same file become one resource.
Then precedence does the rest:
01
Forbid always wins
A matching forbid short-circuits evaluation. No permit, however specific, overrides it.
02
Hold outranks permit
If any rule says a human must see this call, another permissive rule cannot skip that person.
03
Permit allows the call
Only an explicit permit lets the call run.
04
No match means deny
A request that matches nothing is denied with rule: null. An empty rule set denies everything, so a policy that fails to load fails closed.
The output is a decision record, not a boolean: considered traces the rules examined, and remediation gives the agent a legitimate path so it can re-plan instead of retrying.
Decision returned by evaluate()json
{
"verdict": "deny",
"rule": "deny-dotenv-read",
"reason": "Reading .env files is denied outside an approved secrets flow.",
"remediation": "Ask for the value as a secret handle instead.",
"considered": [
{ "rule": "deny-dotenv-read", "effect": "forbid", "matched": true }
],
"resource": "/workspace/.env.production"
}
Enforcement points
Where to enforce it.
A decision nobody enforces is a log line. Cirvix enforces at three points, and each has an edge.
| Enforcement point | Use it when | Boundary |
|---|---|---|
guard.wrap (Node or Python SDK) |
You own the agent's tool functions | Only the returned wrapped tools. Originals still held by an executor are not governed. |
cirvix gateway |
The agent uses MCP servers (Claude Code, Cursor, any MCP client) | Only tools/call requests routed through the gateway. Direct upstream entries and editor built-ins are outside it. |
cirvix runtime control socket |
A cooperating client speaks the local protocol | Only clients that use the socket. Starting it does not instrument an existing agent. |
Cirvix does not intercept all activity on a machine. Subprocesses, unwrapped callables and host processes stay outside the boundary, so host permissions still matter. The smallest in-process example, where the denied call never reaches the tool function:
guard.wrap: enforce in-processjavascript
import { readFile } from "node:fs/promises";
import { guard, CirvixDenied, STARTER_RULES } from "@cirvix_ai/agent-control";
// Register the RETURNED tools with your agent's executor, not the originals.
const tools = guard.wrap(
{ read_file: async ({ path }) => readFile(path, "utf8") },
{ agent: "pr-triage", rules: STARTER_RULES },
);
await tools.read_file({ path: "src/index.mjs" }); // runs
try {
await tools.read_file({ path: ".env.production" }); // refused before the tool is invoked
} catch (err) {
if (!(err instanceof CirvixDenied)) throw err;
console.log(err.policy, err.decisionId); // "deny-dotenv-read", "dec_..."
}
Artifact
A starter policy for a coding agent.
Two prohibitions, one hold, one scoped permit. Everything else is denied by default.
cirvix.policy.jsonjson
{
"rules": [
{
"name": "deny-dotenv-read",
"effect": "forbid",
"actions": ["fs.read", "fs.*"],
"resources": ["**/.env", "**/.env.*"],
"reason": "Reading .env files is denied outside an approved secrets flow.",
"remediation": "Ask for the value as a secret handle instead."
},
{
"name": "deny-workspace-escape",
"effect": "forbid",
"actions": ["fs.*"],
"when": [{ "path": "path.insideWorkspace", "op": "eq", "value": false }]
},
{
"name": "hold-prod-deploys",
"effect": "hold",
"actions": ["k8s.apply", "db.migrate"],
"when": [{ "path": "environment", "op": "eq", "value": "production" }],
"approvers": ["platform-oncall"],
"reason": "Production change. Held for a named approver."
},
{
"name": "allow-workspace-read-write",
"effect": "permit",
"actions": ["fs.read", "fs.list", "fs.write"],
"when": [{ "path": "path.insideWorkspace", "op": "eq", "value": true }]
}
]
}
Read it top to bottom the way the engine does. A read of config/.env.local matches deny-dotenv-read and is denied even though allow-workspace-read-write would also match. A write to a file outside the workspace root is denied by deny-workspace-escape. A k8s.apply with --env production is held for platform-oncall. An outbound HTTP request, or any action you have not named, matches no rule and is denied with rule: null until you write a permit for it. The full rule grammar, including all when operators, is on the policy engine page.
Deployment checklist
Putting your first agent under runtime authorization.
Eight steps, in order. Each one has a check you can run.
01
Inventory what you are about to govern
Run npx @cirvix_ai/agent-control scan, a local, heuristic inventory of agent runtimes, MCP configs and reachable credential paths (findings such as runtime-ungoverned and mcp-broad-scope). Treat it as a map, not proof of what a running agent can do.
02
Pick one agent and one enforcement point
Start with a single agent: guard.wrap if you own its tool code, cirvix gateway if it uses MCP servers.
03
Start from the starter rules, then narrow
With no workspace policy, Cirvix falls back to nine starter rules (print them with cirvix policy --json). Copy what you need into cirvix.policy.json and add the rules specific to this agent's job. Rule names appear in every decision, so make them meaningful.
04
Validate and test the policy like code
Run cirvix policy check --policy cirvix.policy.json, then write tests with evaluate() from @cirvix_ai/agent-control/testing and run them with node --test in CI. Assert the denials and holds you care about, not only the happy path. For policy pull requests, expectNoLoosening from the same module compares a before and after rule set.
05
Wire enforcement so there is no side door
Hand the framework the returned wrapped tools. For MCP, keep upstreams in a separate mcp-upstreams.json and make the gateway the only server the client launches. A direct upstream entry left in the client config is an ungoverned route.
06
Give every hold a named approver
Use hold for consequential actions such as production deploys, migrations and deletes. validateRules flags a hold without approvers. Approvals are local records that authorize a retry; they are not authenticated signatures, and nothing resumes on its own.
07
Turn on a record and verify it
Supply an AuditChain sink (Node) or an on_decision callback (Python). Generate a benign allowed call and a denied one, then run cirvix audit verify and cirvix logs. Confirm the expected decision IDs and a non-zero record count, because an empty or missing file verifies as an empty chain.
08
Expand one rule at a time
Add the next agent or tool once the first runs cleanly. Because forbid always wins, a new permit cannot open a hole through an existing prohibition.
Try it
Test a decision before you install anything.
check evaluates one hypothetical call. It reads no file, runs no tool and writes no audit record.
Terminalbash
# Node 20+. No account. Nothing is read or executed by `check`.
npx --yes @cirvix_ai/agent-control check --action fs.read --resource .env.production
# DENY fs.read /workspace/.env.production
# rule deny-dotenv-read
npx @cirvix_ai/agent-control check --action fs.read --resource src/index.mjs
# PERMIT fs.read /workspace/src/index.mjs
# rule allow-workspace-read (exit 0)
# With the policy above, a production deploy is held, not run:
npx @cirvix_ai/agent-control check --policy cirvix.policy.json \
--action k8s.apply --resource production/checkout --env production
# HOLD k8s.apply /workspace/production/checkout
# rule hold-prod-deploys
# waits platform-oncall (exit 0: a hold is not permission)
Note the exit codes: check exits 1 on deny and 0 on both permit and hold, so exit status alone is not authorization to execute. When you are ready to enforce, install the package with npm install @cirvix_ai/agent-control (or pip install cirvix for the Python evaluator) and follow the quickstart. For MCP clients, the MCP guide and our article on where to put the MCP authorization boundary cover the gateway setup.
Honest limits
What runtime authorization does not do.
Stated plainly so you can design around it.
01
It does not prevent prompt injection
It limits what a manipulated agent is permitted to do afterward.
02
It cannot evaluate what it never sees
Coverage is exactly the set of calls routed through the wrappers, the gateway or the socket. Remove alternate routes.
03
It honours the policy you wrote
A permissive rule is applied as written. Test rules in CI.
04
The audit chain is tamper-evident, not tamper-proof
SHA-256 chaining detects internal inconsistencies. Detecting tail deletion or a fully recomputed history needs a trusted external checkpoint. A record shows authorization, not that the tool succeeded.
FAQ
Runtime authorization, asked directly.
Short answers.
01
What is runtime authorization for AI agents?
A per-action decision, made on the execution path, about whether a specific agent may perform a specific action on a specific resource in the current context. The decision happens after the agent proposes the call and before the tool runs, so a refused action never produces a side effect.
02
How is runtime authorization different from API scopes or IAM roles?
Scopes and roles are granted once, at configuration time, to a credential. Runtime authorization is evaluated on every call, with the resource and context of that call. The two compose: IAM decides which credentials exist; runtime authorization decides whether this use of them may run now.
03
Does runtime authorization stop prompt injection?
No. Injection happens in text the model reads, and Cirvix does not detect it. Runtime authorization limits what a manipulated agent can do next: the harmful tool call it attempts is still evaluated against policy and can be denied or held.
04
Does Cirvix see everything an agent does?
No. Every governed agent action routed through Cirvix is evaluated before execution. Calls that do not pass through the returned SDK wrappers, the MCP gateway or the local control socket, such as editor built-in tools or arbitrary subprocesses, are outside that boundary.
05
What does it cost to try?
The local engine is Apache-2.0 and the free tier needs no account: one governed agent and 100 policy decisions a day. npx @cirvix_ai/agent-control scan and check run locally. Paid tiers and the hosted control plane are in early access.
Start where you are
Put one agent under policy today.
Run the free local scan with no account, test a decision with check, then wrap one agent's tools.
Read the quickstart →AI agent authorization walkthroughPricing
Originally published on cirvix.com. Cirvix is open source: try it locally with npx @cirvix_ai/agent-control scan or see github.com/CIRVIX/agent-control.
Top comments (0)