---
title: "Capability: Agent permissions"
canonical_url: "https://tokenobserve.com/platform/agent-permissions"
published: 2026-09-02
last_updated: 2026-10-21
---

# Capability: Agent permissions

Deny by default, explicit deny wins, and delegation intersects, so an agent cannot borrow authority it was never granted.

## What it is

Cloud workspaces use managed portal identity; customer-hosted identity-provider and child identity-management configuration below is not exposed in the self-service cloud portal. Token Observe decides every model call and tool call passing through it against action-level permissions that are deny-by-default: a support agent may hold an allow on tool:orderdb/get_details while tool:payments/issue_refund is absent, and therefore denied. An explicit deny beats every allow wherever it is written. When one agent delegates to another the chain intersects rather than unions: every hop must allow, so a low-privileged agent gains nothing by routing work through a higher-privileged one. The on-behalf-of header does the same once you switch it on; until then it is attribution and nothing else.

## Facts

- Default: Deny. An action no role names is refused
- Precedence: An explicit deny beats every allow, in any role
- Resources: model:gpt-5o-mini and tool:orderdb/* with wildcards
- Delegation: Every hop must allow; the chain intersects

## The limit

On-behalf-of mask: Off by default; only enforce refuses anything

## Why it exists: An agent holds whatever you forgot to withhold.

An agent authenticates with an opaque bearer token, and any process holding that string is the agent. There is no cryptographic binding to a workload, and the product carries that as a stated residual risk. The permission set is therefore the control that matters, and the difference between a contained incident and a bad week is whether the roles are an allowlist or a list of exceptions.

Conventional role models fail agents twice. A role granting a system rather than an action hands over that whole system, so an agent needing one read against the order database also gets the refund endpoint beside it. And agents call other agents: a model silent about the second hop lets a low-privileged agent ask a higher-privileged one to do what it was just refused.

## How it works

- Name the resource: The thing authorised is a string: model:<name> for a model call, tool:<server>/<tool> for a tool call. One vocabulary covers both surfaces, so tool:orderdb/* scopes an entire MCP server in one line.
- Deny first, then require an allow: Every role and permission is scanned, and the first matching deny returns immediately, naming the role and the pattern; order is irrelevant. With no deny, the first matching allow decides; with no allow either, the refusal reason ends in deny by default.
- Intersect every hop, and the named human: Where the request names upstream agents, each role set is a link and every link must allow; the refusal names the earliest failing link and how many there were. Once on-behalf-of enforcement is on, the named person’s mapped roles are appended as the last link of that same chain.

## Where it runs in the request path

Steps 6 and 6b of the request path

## What it does not do

- There is no role inheritance. Roles are flat permission lists, and a superset role has to restate what it supersets.
- There are no time-bound or just-in-time grants. A permission stands until somebody edits the role, and nothing expires on its own.
- Permissions do not read arguments. Refunds over £200 need approval is a policy, not a permission: this layer knows the tool, not the amount.
- Only one verb is issued. Everything is evaluated as invoke; the action field is matched and the wildcard honoured, but a finer verb set is reserved.
- The on-behalf-of header carries no signed claim. It narrows and never grants, and an agent that omits it skips the mask unless configured to require a principal.
- Group claims are a snapshot from the person’s last sign-in, not a live directory read, and are refused once older than the window (24 hours by default). Entra ID stops emitting the claim past roughly 200 groups and the directory link is not followed, so those requests are refused. Okta needs the groups scope granted. Google Workspace emits no group claim at all, so the intersection is unavailable there.

## Read next

- https://tokenobserve.com/platform/agent-registry
- https://tokenobserve.com/platform/policy-engine
- https://tokenobserve.com/platform/mcp-gateway

## Questions and answers

Q: What happens when one role allows an action and another denies it?
A: The deny wins, regardless of order. The evaluator returns at the first matching deny and names the role and pattern that produced it. That is what makes a subtractive guardrail reliable: grant a whole tool namespace to a team, then remove one action from one agent without rewriting the broad grant.

Q: Can a low-privileged agent gain access by asking a higher-privileged agent?
A: No. The effective permission set is the intersection of every agent in the chain, so the request is refused at the first link without the grant, and the refusal records which link out of how many. The chain is asserted rather than proven, but because it can only add links and every link must allow, forging it buys an attacker less than sending none.

Q: Does naming a human in the on-behalf-of header restrict what an agent can do?
A: Only once you switch enforcement on, and the default is off, under which nothing is looked up at all. Under shadow the intersection is computed and recorded as what it would have refused. Under enforce the person’s mapped roles join the delegation chain and can only narrow. Two of the three settings refuse nothing, and an install that has not reached enforce should not call the intersection a control it holds.

The long-form detail on this capability (the anchored sections, the worked policies and the error envelopes) is on the page itself at https://tokenobserve.com/platform/agent-permissions.
