Agentic AI RBAC defines which tool an agent may call, on whose behalf, and against which resource. An agent can suggest an action; the server must decide whether that specific action is permitted. The useful boundary is the intersection of the initiating principal’s authority, the tool’s allowed operations, the target tenant and any resource-specific conditions.
This guide covers six application design patterns: principal model, per-tool scoping, tenant isolation, request-context binding, attribute-based extensions and policy-engine integration. They compose with authentication, database controls and runtime isolation. A prompt instruction or hidden tool description cannot substitute for an enforced permission check.
Updated October 4, 2026: the implementation below is a tested JavaScript policy example, with simulated identity and storage adapters. It is not a complete OAuth server, MCP server or production deployment. The earlier illustrative audit-completeness percentages and unsupported incident-frequency claims have been removed; neither measured the security of these patterns.
- 01Authorise every operation.A valid login or MCP connection does not authorise every tool call. Require an explicit allow for the action, resource, tenant and current principal before executing the handler.
- 02Carry identity without forwarding arbitrary tokens.Record both the initiating user and agent workload. Obtain credentials for each intended audience; an MCP token is not a downstream API token.
- 03Tenant boundaries start in the data layer.Derive tenant membership from trusted server state. Scope resource lookup, retrieval, memory and caches; process separation alone does not prevent leaks.
- 04A signature is only one check.Validate issuer, audience, expiry and trusted claims. Signing context does not prevent replay or prove that a requested action matches the user’s intent.
- 05Make denial paths executable.The worked example rejects cross-tenant resources, inactive membership, unknown actions, missing scopes and insufficient roles. Tests also verify that denied calls never reach the handler.
01 — Why RBACAgents are credentialed — RBAC defines the blast radius.
A tool’s credential is a capability. A read token can expose confidential material, while a write token can alter it. If a shared service account can access every customer’s records, checking that a user is logged in does not narrow that account’s authority. The tool wrapper must constrain what the credential is used to do.
OWASP’s Excessive Agency guidance distinguishes excessive functionality, permissions and autonomy. That is a useful threat model: reduce the operations exposed, reduce their permissions and require a separate confirmation where consequences justify it. Those controls address different failure paths; rate limiting and logging cannot make an unauthorised action authorised.
For a user-driven workflow, a useful design rule is: allow an operation only when the user may perform it, the agent workload may invoke that tool, the tool supports that operation and the resource belongs to the authorised tenant. Add conditions for classification, ownership or confirmation where needed. RBAC supplies the role-to-operation mapping; the remaining constraints are tenant and attribute checks, not properties RBAC automatically supplies.
Fail closed means a missing policy, absent attribute, expired credential or unavailable decision service prevents execution. Return a controlled error and log the denial. Do not substitute the service account’s privileges because the policy engine is slow, and do not retry with a broader token without an explicit authorised escalation flow.
Start the review at each execution path: HTTP routes, MCP dispatch, background workers, retry queues and direct SDK access. A well-protected orchestration gateway is insufficient if the same agent can bypass it using a shell command and an exposed credential. Inventory reachable credentials and destinations as well as registered tools. Our agent runtime boundary reference addresses that adjacent runtime question; its capability inventory is separate from application authorisation.
Security and compliance requirements depend on the workload and jurisdiction. This guide recommends per-action attribution and least privilege, but it does not establish a legal requirement to use a particular delegation pattern. For an implementation review, AI transformation services can help connect these checks to the actual application and its data boundaries.
02 — Principal PatternAgent-as-principal vs on-behalf-of.
The principal model answers who is acting. Agent-as-principal uses a workload identity with its own permissions. On-behalf-of, or OBO, preserves the initiating user in the authorisation path. A hybrid supports both, but it must make the distinction explicit rather than quietly switching to a powerful service credential when delegation fails.
Autonomous maintenance can legitimately use a service principal. Record its job owner, workload identity and triggering event, and restrict it to that task. A user-facing tool may also execute with a service credential if the application reliably checks the user’s permissions before access and preserves both identities in its audit records. That is application-level enforcement; it is not automatically downstream delegated access.
For OBO, authenticate the user and workload separately where the architecture requires it. Resolve the user’s current membership and roles, obtain a delegated token for the target service where supported, and enforce the operation at the receiver. A JWT containing a user ID is insufficient unless the receiver trusts its issuer and validates the appropriate claims. Do not describe a locally minted assertion as a vendor-supported token exchange.
The MCP 2025-11-25 authorisation specification defines an HTTP transport flow. Authorisation is optional for MCP implementations; when supported, HTTP transports should follow that flow. Stdio implementations should instead obtain credentials from the environment. Transport authentication does not define your application’s tenant roles or authorise every resource operation.
A protected MCP server validates a token issued for its own audience. The MCP security guidance prohibits token passthrough: do not accept an arbitrary upstream API token and forward it downstream. Obtain separate target-service credentials through that service’s supported flow. Bind the initiating identity to your internal request record without treating one bearer token as universal authority.
Workload principal
Use a dedicated service identity for a bounded background task. Record the trigger and owner. Tenant-scoped jobs still need explicit tenant checks; autonomy does not grant access to every customer.
Delegated user + workload
Preserve the user and agent identities, then enforce the user’s current permissions at every tool boundary. Use downstream delegation only where the target API supports it.
Explicit hybrid paths
Keep background and user-driven credential paths separate. A failed delegated call must not fall back to a shared administrator credential. Review each path independently.
Shared administrator
One broad credential serving all users enlarges the effect of a compromised wrapper. A log entry naming a user does not limit the credential or enforce the user’s rights.
OBO improves attribution when the identity survives every boundary, but it does not prove intent. A prompt injection can request an action the user technically has permission to perform. For sensitive actions, bind a separate confirmation to the exact action, target and arguments. Merely displaying a generic approval dialog does not prevent the agent from changing the operation after confirmation.
03 — Per-Tool ScopingLeast privilege at the tool level.
Per-tool scoping separates useful operations. A document reader should not acquire delete access just because another tool needs it. A draft creator should not expose a send operation. Use narrower upstream credentials where supported; where the provider only offers broad scopes, disclose that limitation and enforce the smaller operation set in the wrapper.
Define tool names, actions, accepted argument schemas and required permissions in server-owned configuration. Never take a required scope from model-supplied arguments. Hiding a tool from discovery is useful interface design, but the execution handler must still reject a direct call without permission. Likewise, a read-only annotation is descriptive unless a receiver actually enforces the restriction.
The example below grants a viewer document-read access and an editor read plus draft-update access. It has no implicit administrator inheritance, wildcard action or send operation. A user needs both the role and the tool scope, plus active tenant membership and request binding. These are deliberately small example roles, not a standard role taxonomy or a complete document-sharing policy.
Prerequisites: JavaScript on Node.js 24, an upstream authentication adapter, authoritative membership lookup and tenant-scoped storage. The function receives trusted, already verified context and an authoritative resource row. It does not verify OAuth tokens itself. The code was executed on Node.js 24.21.0; the test harness extracts this exact snippet rather than testing a separate implementation.
function allow(ctx, action, resource, nowMs) {
const text = (v) => typeof v === "string" && v.length > 0;
const strings = (v) => Array.isArray(v) && v.every(text);
if (!ctx || !resource || !Number.isFinite(nowMs)) return false;
if (![ctx.userId, ctx.workloadId, ctx.tenantId, ctx.sessionId, ctx.requestId,
ctx.resourceId, ctx.action, resource.id, resource.tenantId,
resource.kind].every(text)) return false;
if (!Number.isFinite(ctx.expiresAtMs) || ctx.expiresAtMs <= nowMs)
return false;
if (ctx.membershipActive !== true || !strings(ctx.roles) ||
!strings(ctx.scopes)) return false;
if (resource.kind !== "document" ||
ctx.tenantId !== resource.tenantId ||
ctx.resourceId !== resource.id || ctx.action !== action) return false;
const rules = new Map([
["document.read", { scope: "docs:read", roles: ["viewer", "editor"] }],
["document.draft.update", { scope: "docs:draft", roles: ["editor"] }],
]);
const rule = rules.get(action);
return !!rule && ctx.scopes.includes(rule.scope) &&
ctx.roles.some((role) => rule.roles.includes(role));
}For example, an editor in tenant A with the draft scope can update the bound document in tenant A. The same editor cannot update tenant B’s document, use a read-only token to write, or substitute a different resource after approval. Adding an admin string to the input does not bypass a missing permission. Unknown actions and absent fields return false.
Restrict the upstream credential independently. The policy function only governs paths that call it; it cannot constrain a leaked provider token or a separate handler that ignores its result. Use protected execution paths, database controls and secret isolation to keep the agent from reaching alternative access routes. The MCP server security guide discusses the surrounding server hardening topics.
Review unused privileges against actual requirements and logs. No fixed quarterly schedule or claimed percentage of dormant scopes proves that a credential is safe. Rate limits can slow repeated misuse, but a single authorised-looking read can disclose sensitive data. Permissions, confirmation, rate limits and audit records each need their own acceptance tests.
Limit retrieval
Enforce resource ownership and tenant filters even for read-only credentials. Read access is a confidentiality boundary, not a harmless default.
Separate effects
Distinguish draft editing from sending, deleting or publishing. Confirm the precise operation where required, and recheck mutable state before writing.
Separate approval
Administrative automation needs a separate authorised path with narrow credentials and stronger confirmation. Natural-language input alone does not justify broad admin access.
04 — Tenant IsolationPer-tenant segregation without cross-tenant leakage.
Tenant isolation is required as soon as a workload serves multiple tenants. Resolve tenant membership from the authenticated session and server-owned membership records. A tenant ID supplied by the model, a URL parameter or an unverified header is a selector to validate, not an authority. Load the resource inside that tenant’s scope and compare its authoritative ownership before returning content.
A separate process can reduce shared-memory risks, but it is not a guarantee of zero cross-tenant risk. Two processes can still share a powerful credential, storage bucket, vector database or logging destination. Shared processes can enforce meaningful data isolation through scoped queries and database policies. Pick isolation mechanisms against a threat model rather than ranking process separation as the only safe option.
Partition retrieval, caches, conversation history and intermediate artefacts. A shared vector index does not inevitably leak: the problem is a missing or bypassable authorisation filter. Apply the tenant and resource-access restrictions before content enters model context, not after the generated answer. Check access to retrieved documents as well as the tenant tag, because users within one tenant may have different rights.
Cache keys should incorporate the relevant tenant, principal or access-policy dimensions. A tenant-only cache can expose one user’s private answer to a different user in the same organisation. Recheck access when permissions change, and avoid carrying old retrieved content into a newly scoped session. Tenant isolation at storage does not remove data already present in an agent’s conversation buffer.
Separate execution
Separate tenant processes or containers where the risk warrants it. Review credentials, mounts, network access and storage independently; a process boundary alone does not establish full isolation.
Scoped storage access
Use per-tenant credentials or validated tenant-scoped queries, with database enforcement where supported. A shared process must not let model arguments choose the access context.
Scoped memory and caches
Keep retrieval, conversations, cached results and queued work inside the intended access scope. A tenant tag in a log or prompt does not enforce an access rule.
Test both the obvious cross-tenant resource ID and the less obvious paths: a cached answer, a stale conversation, a retry after role removal and an export containing several records. Assert that no forbidden content reaches the model and that denied writes do not reach the handler. Do not rely solely on a model refusing to mention a secret it has already received.
The JavaScript policy compares the bound resource and tenant. Its storage adapter must also query by tenant and enforce the current user’s resource access rules. The example’s role policy intentionally permits readers to read any document in their own tenant; it is unsuitable unchanged for applications with private documents or per-document sharing.
05 — Request ContextBinding every call to the originating user.
Request-context binding connects a verified principal to a particular operation. Useful context includes user, workload, tenant, session and request identifiers, the target resource, allowed action and expiry. Populate it at trusted request entry, not from the tool arguments. Preserve trace IDs through queues, but refresh mutable permissions before execution rather than treating an old role snapshot as permanent authority.
Across service boundaries, use a supported authenticated channel or a properly validated assertion. For JWTs, validate issuer, audience, permitted algorithms and expiry with a maintained verification library. RFC 8725 explains why signature validation alone is insufficient and why validation rules need to match the token’s intended use. A decoded payload is not a verified identity.
A signature prevents undetected modification only under its cryptographic trust assumptions. It does not prevent replay of a valid signed object, prove a current membership or tie an action to consent. For state-changing work, use operation-specific confirmation, expiry and appropriate replay or idempotency controls. Where arguments affect the consequence, bind their canonical representation or digest as well as the resource ID.
The wrapper below demonstrates where checks sit. Its adapters are simulated in the tests: authenticate must validate credentials, resolve current membership and produce action-bound context; loadDocument must perform an authoritative tenant-scoped lookup. Neither function may trust a model-provided tenant or role. The selected handler is server-owned and receives the checked context and row.
async function runTool(request, adapters) {
const { action, resourceId } = request ?? {};
if (typeof action !== "string" || typeof resourceId !== "string" ||
resourceId.length === 0) throw new Error("Denied");
const handlers = new Map([
["document.read", adapters.readDocument],
["document.draft.update", adapters.updateDraft],
]);
const handler = handlers.get(action);
if (typeof handler !== "function") throw new Error("Denied");
const ctx = await adapters.authenticate(request);
const resource = ctx ? await adapters.loadDocument(
ctx.tenantId, resourceId
) : null;
if (!allow(ctx, action, resource, adapters.now()))
throw new Error("Denied");
await adapters.audit({ requestId: ctx.requestId, userId: ctx.userId,
tenantId: ctx.tenantId, workloadId: ctx.workloadId, action,
resourceId: resource.id, decision: "allow", policyVersion: "example-v1" });
return handler({ ctx, resource });
}The wrapper never calls a handler after denial. Dependency failures propagate rather than falling back to broader authority. This small example logs allowed decisions before execution; production code also needs controlled authentication errors, redacted denial and error events, outcome records and a chosen response to audit-service failure. Do not log bearer tokens or whole prompts to establish attribution.
Keep the check and effect consistent. A resource owner, classification or membership can change between lookup and write. Use an appropriate transaction, conditional update or data-layer permission check at the moment of execution. The handler in this example is an adapter, so the unit tests do not establish transaction safety, replay prevention, token verification or a deployed MCP integration.
A decision record can include actor, workload, tenant, action, resource, request ID, policy version and outcome. Store sensitive decision inputs only under a deliberate redaction and retention policy. Our agent audit-trail design discussion is related reading on event structure.
06 — ABAC ExtensionsAttribute-based for fine-grained control.
RBAC maps roles to operations; ABAC adds trusted attributes about the principal, resource and environment. Examples include document classification, owner, membership status and the presence of an operation-specific confirmation. Add attributes when a role alone would permit an operation that should be conditional. A regulated workload does not automatically require a particular engine or a fixed maturity tier.
Establish the authority for every attribute. Classification comes from the resource store, membership from the identity or tenant directory, and confirmation from the application’s verified approval record. A model’s risk score, a client’s isAdmin flag or a natural-language claim that a task is urgent is not a trusted policy input. Missing classification should not silently become public.
The policy example uses membership and resource ownership constraints alongside its roles. To extend it for confidential documents, define exactly which roles may access that classification and deny unknown values. For a send operation, require an approval record bound to recipients and content, not a reusable boolean saying that the user once approved an email. Update both the allowed and denied test cases whenever a rule changes.
Keep role inheritance explicit. A platform administrator is not automatically a tenant document editor, and a tenant editor is not automatically an organisation administrator. When a principal has several roles, define whether permissions combine and whether an explicit deny overrides an allow. Review cross-tenant administration as a separate capability instead of teaching the model to add a special role string.
Relationship-based authorisation is useful for sharing: a user may view a document through team membership or an editor relation. It complements role and attribute checks rather than replacing tenant ownership or workload restrictions. A relationship lookup that returns true for viewing a document does not by itself authorise deleting it or invoking any tool the agent chooses.
Coarse operation grants
Map explicit roles to actions. Test unknown roles, combinations and inheritance. No administrator bypass appears in the worked policy.
Resource and context constraints
Combine role grants with trusted ownership, classification and current membership. Deny absent required values rather than selecting a permissive default.
Sharing and membership
Model document or team relationships when the product needs them. Keep tenant and operation restrictions in the final decision even when a relationship grants access.
A rollout sequence is a planning aid, not a security score. A single-tenant pilot can start with a small explicit role map. A multi-tenant pilot needs tenant isolation immediately. Add attributes and relationships for actual product requirements, then test revocation, denial and unavailable-policy behaviour. More policy layers do not automatically mean stronger enforcement.
07 — Vendor IntegrationCerbos, OPA, Zanzibar-style systems.
Cerbos, Open Policy Agent and relationship systems are different ways to implement a decision service. Choose against the data model and existing infrastructure, not an unsupported claim that one is fastest or becomes necessary after a particular number of rules. A small, centrally tested application policy can be appropriate; scattered inconsistent checks are the failure to avoid.
Cerbos’s CheckResources API accepts a principal, resources and requested actions and returns action decisions. Provide tenant-scoped roles and authoritative resource attributes. Check the requested action’s explicit allow result; a successful API response or a decision for a different resource is not permission to execute.
OPA’s Data API evaluates a selected policy document using an input object. A successful response can lack result when a document is undefined. Define a default-deny decision and require the exact expected boolean true; transport success, a truthy object or an absent result must not become allow. Confirm the policy path and response schema in the deployed version.
OpenFGA’s role and permission models express access through relationships. Google’s Zanzibar paper is the underlying research reference, not a plug-in agent API or a guarantee that another system has identical consistency behaviour. Specify the model, tenant object structure and revocation expectations for the engine you actually operate.
Resource and action policies
Evaluate application roles and attributes against a principal and resource. Check every requested action’s decision and handle errors as denial.
General-purpose decisions
Use a deliberate policy path and input schema. Define default deny, validate the response type and accept only the intended explicit allow result.
Sharing models
Use a relationship engine when team or document sharing drives access. Define tenant separation, model version and consistency expectations outside the LLM.
Small explicit rule set
Keep checks central, tested and consistently invoked. Migrate when management or policy complexity warrants it, not at an invented rule-count threshold.
The worked snippets are library-independent JavaScript. Cerbos, OPA and OpenFGA were not installed or called, and no live provider credentials were used. Their API notes describe current official documentation checked October 4, 2026; they are not tested vendor integrations. Before connecting an engine, test the exact SDK or REST response and exercise missing policies, timeout, malformed output and stale membership paths.
The local tests execute both snippets and check allowed reads and draft updates, cross-tenant denial, resource/action substitution, missing scopes, insufficient roles, inactive membership, malformed context and expiry. Wrapper tests verify that denial and dependency failure leave handler counts at zero. That is evidence about this example’s logic, not a measured reduction in incidents or proof of production security.
For rollout, record which component verifies identity, which resolves membership, which owns resource attributes, which checks policy and which performs the effect. Run an end-to-end denial test through each entry point, including queued retries. Keep a versioned policy fixture so reviewers can reproduce what was checked, and verify revocation behaviour in the real store rather than assuming a signed context updates itself.
Enforce the boundary before the tool executes.
Choose the principal model, narrow the tool operation, enforce tenant ownership, bind verified request context, add trusted attributes and integrate a consistent decision service. These six patterns are design choices with explicit assumptions; they do not guarantee a successful security review or eliminate prompt injection.
The practical acceptance test is whether a forbidden operation reaches the handler or exposes content to the model. Start with a small explicit policy, test the deny paths, then verify the identity and data adapters in your real stack. Keep confirmation, replay protection, transaction safety and runtime isolation visible as separate requirements.
A user’s permission to access a system does not mean every agent invocation should inherit every available credential. Evaluate the particular action and resource at execution, preserve both user and workload attribution, and refuse uncertain decisions. That is the boundary the implementation must make concrete.