Capability-Based Security for LLMs
When it comes to securing AI agents, most teams reach for what they know: Role-Based Access Control (RBAC). It's the standard for web applications, enterprise systems, and cloud infrastructure. But RBAC was designed for human users with predictable behavior patterns—not for AI agents that generate actions dynamically, operate autonomously, and need permissions that change based on context.
Capability-based security offers a fundamentally better model for AI authorization. Instead of asking "What role does this agent have?" it asks "What specific capabilities does this agent hold right now?"
This shift from identity-based to token-based authorization is what makes AI agents both powerful and safe.
Understanding RBAC: Why It Works for Humans
Role-Based Access Control is simple and elegant. You define roles, assign permissions to roles, and assign roles to users.
# Traditional RBAC
roles:
customer_support:
permissions:
- read_customers
- create_tickets
- update_tickets
sales_rep:
permissions:
- read_customers
- create_quotes
- update_opportunities
admin:
permissions:
- read_customers
- create_customers
- delete_customers
- manage_users
- system_config
users:
alice:
roles: [customer_support]
bob:
roles: [sales_rep]
carol:
roles: [admin]
RBAC works for humans because:
- Roles are stable - Alice is always a support rep during her shift
- Permissions are predictable - Support reps always need the same access
- Context rarely changes - Alice's permissions don't depend on the time of day, the customer she's helping, or how many tickets she's processed today
- Humans self-regulate - Alice won't read every customer record just because she can
Why RBAC Fails for AI Agents
AI agents break every assumption RBAC relies on.
Problem 1: Agents Need Dynamic Permissions
RBAC assigns static permissions at deployment time. But AI agents need permissions that change based on:
- Who they're helping: Access only the current customer's data
- What they're doing: Different constraints for reads vs. writes
- When they're acting: Tighter limits outside business hours
- How much they've done: Rate limits based on cumulative usage
# RBAC can't express this:
"Agent can read customer data, but ONLY for the customer
currently being served, ONLY during business hours,
ONLY if the agent hasn't exceeded 100 reads today,
and ONLY columns that aren't sensitive."
# RBAC gives you:
permissions: ["read_customers"]
# Which means: read ANY customer, ANY time, ANY column, unlimited
Problem 2: Roles Are Too Coarse
RBAC groups permissions into roles. But agents often need unique combinations that don't fit neatly into roles.
Example: A customer support agent that can:
- Read customer data (like a support rep)
- Process refunds under $500 (like a billing agent)
- Send emails to company domains (like a communications agent)
- Cannot modify customer records (unlike a support rep)
- Cannot process refunds over $500 (unlike a billing agent)
With RBAC:
# Option 1: Create a custom role (role explosion)
roles:
ai_support_limited_billing_restricted_email:
permissions: ["read_customers", "refund_under_500", "email_company_only"]
# Option 2: Assign multiple roles (over-permission)
agent_roles: ["support", "billing", "communications"]
# But now the agent has ALL permissions from ALL three roles
Neither option works well. Custom roles lead to role explosion. Multiple roles lead to over-permissioning.
Problem 3: No Constraint Enforcement
RBAC says "you can do this." It doesn't say "you can do this, but only under these conditions."
# RBAC
permission: process_refund # No limit on amount, frequency, or conditions
# What you actually need:
permission: process_refund
constraints:
maxAmount: 500
maxPerDay: 10
requireApproval: amount > 100
onlyForCustomer: "{{session.customer_id}}"
notDuring: "maintenance_window"
RBAC has no mechanism for constraints.
Problem 4: No Delegation Model
In multi-agent systems, agents need to delegate specific capabilities to other agents. RBAC has no concept of delegation.
# Manager agent needs to delegate "read_customer" to worker agent
# But only for customer #12345, only for 10 minutes
# RBAC: Assign the worker agent the same role
# Problem: Worker now has ALL permissions of that role, permanently
# Capabilities: Delegate a specific, scoped, time-limited token
# Worker gets exactly what it needs, nothing more
Problem 5: Revocation Is All-or-Nothing
With RBAC, you revoke an entire role. You can't revoke a single permission without changing the role definition (affecting all agents with that role).
# Agent misbehaves with email sending
# RBAC: Revoke "support" role → Agent loses ALL support permissions
# Capability: Revoke email capability → Agent keeps other permissions
Capability-Based Security: A Better Model
Capability-based security replaces roles with tokens that embody specific permissions.
Core Concepts
1. A capability is an unforgeable token that grants specific access
capability_token:
id: "cap_abc123"
agent: "support-bot-01"
granted: "2026-02-15T09:00:00Z"
expires: "2026-02-15T17:00:00Z"
capabilities:
- action: read_customer
resource: "customer://id:{{session.customer_id}}"
constraints:
columns: ["name", "email", "order_history"]
maxRows: 50
- action: process_refund
resource: "order://customer:{{session.customer_id}}/*"
constraints:
maxAmount: 500
maxPerDay: 10
requireApproval: "amount > 100"
- action: send_email
resource: "email://domain:@company.com"
constraints:
maxPerHour: 20
blockSensitiveData: true
2. Capabilities are context-aware
The same agent gets different capabilities based on context:
# Morning shift: Full capabilities
morning_token = act.issue_token(
agent="support-bot",
context={"shift": "morning", "customer_id": "12345"},
policy="support-full"
)
# After hours: Read-only capabilities
afterhours_token = act.issue_token(
agent="support-bot",
context={"shift": "after_hours", "customer_id": "12345"},
policy="support-readonly"
)
# High-risk customer: Enhanced restrictions
highrisk_token = act.issue_token(
agent="support-bot",
context={"shift": "morning", "customer_id": "vip_99", "risk": "high"},
policy="support-restricted"
)
3. Capabilities can be delegated with attenuation
When one agent delegates to another, the delegated capability can only be equal or more restricted—never more permissive.
# Manager agent has broad capabilities
manager_cap = {
"action": "read_customer",
"resource": "customer://region:US/*",
"constraints": {"maxRows": 1000}
}
# Manager delegates to worker with restrictions
worker_cap = manager_cap.attenuate({
"resource": "customer://region:US/state:CA/*", # More restricted
"constraints": {"maxRows": 100}, # More restricted
"expires": "10m" # Time-limited
})
# Worker CANNOT escalate beyond what manager delegated
4. Capabilities are independently revocable
# Revoke ONLY the email capability
act.revoke_capability(token_id="cap_abc123", action="send_email")
# Agent can still read customers and process refunds
# Revoke ALL capabilities for an agent
act.revoke_all(agent="support-bot-01")
# Instant, global effect
RBAC vs. Capabilities: Side-by-Side Comparison
| Aspect | RBAC | Capability-Based (ACT) | |--------|------|----------------------| | Permission model | Role → Permissions | Token → Specific capabilities | | Granularity | Action-level | Action + Resource + Constraints | | Context awareness | None (static) | Full (dynamic per-request) | | Delegation | Not supported | Built-in with attenuation | | Revocation | All-or-nothing (role) | Per-capability granularity | | Constraint support | None | Amounts, rates, time, conditions | | Scaling | Role explosion | Policy composition | | Audit trail | Role-level | Action-level with full context | | Time-based | Manual role changes | Built-in expiration |
Real-World Scenarios
Scenario 1: E-commerce Customer Support
RBAC approach:
role: customer_support
permissions: [read_customers, read_orders, create_tickets, process_refunds]
# Problems:
# - Can read ANY customer (not just current one)
# - Can process ANY refund amount
# - No rate limits
# - Same permissions 24/7
Capability approach:
token:
capabilities:
- action: read_customer
resource: "customer://id:{{session.customer_id}}"
constraints: {excludeColumns: ["ssn", "payment_info"]}
- action: read_order
resource: "order://customer:{{session.customer_id}}/*"
constraints: {maxRows: 50}
- action: process_refund
resource: "order://customer:{{session.customer_id}}/*"
constraints: {maxAmount: 500, maxPerDay: 5, requireApproval: "amount > 100"}
expires: "8h"
context: {shift: "day", tier: "standard"}
Scenario 2: Financial Analysis Agent
RBAC approach:
role: financial_analyst
permissions: [read_accounts, read_transactions, generate_reports]
# Problems:
# - Can see ALL accounts (compliance violation)
# - No limit on data volume
# - Can generate unlimited reports
Capability approach:
token:
capabilities:
- action: read_account
resource: "account://portfolio:{{agent.assigned_portfolio}}"
constraints:
columns: ["id", "name", "balance", "type"]
excludeColumns: ["tax_id", "beneficiary_ssn"]
- action: read_transaction
resource: "transaction://account:{{agent.assigned_accounts}}/*"
constraints:
dateRange: "last_90_days"
maxRows: 10000
- action: generate_report
resource: "report://portfolio:{{agent.assigned_portfolio}}"
constraints:
maxPerDay: 10
format: ["pdf", "csv"]
classification: "internal_only"
expires: "1d"
context: {compliance_level: "SOX", reviewer: "[email protected]"}
Scenario 3: Multi-Agent Research System
RBAC approach:
# All agents get the same role
role: researcher
permissions: [web_search, read_documents, write_documents]
# Problems:
# - All agents have same permissions
# - No isolation between agents
# - No delegation tracking
Capability approach:
# Each agent gets specific capabilities
coordinator_token:
capabilities:
- action: web_search
constraints: {maxQueries: 100, safeDomains: true}
- action: delegate
constraints: {maxDelegationDepth: 2, attenuationRequired: true}
researcher_token:
capabilities:
- action: web_search
constraints: {maxQueries: 20} # Attenuated from coordinator
- action: read_document
resource: "doc://project:{{project.id}}/*"
writer_token:
capabilities:
- action: write_document
resource: "doc://project:{{project.id}}/drafts/*"
constraints: {maxLength: 10000, requireReview: true}
Implementing Capability-Based Security with ACT
Step 1: Define Your Capability Policies
# policies/support-agent.yaml
policy:
name: "customer-support-standard"
version: "2.0"
capabilities:
read_customer:
resources: ["customer://id:{{session.customer_id}}"]
constraints:
columns: ["name", "email", "phone", "order_history"]
maxRows: 50
rateLimit: "200/hour"
create_ticket:
resources: ["ticket://agent:{{agent.id}}/*"]
constraints:
maxOpen: 10
requiredFields: ["subject", "description", "priority"]
send_email:
resources: ["email://domain:@company.com"]
constraints:
maxPerHour: 20
maxRecipients: 5
blockSensitiveData: true
circuit_breaker:
maxViolations: 3
window: "5m"
suspendDuration: "30m"
audit:
logAll: true
retainDays: 90
Step 2: Issue Capability Tokens
from act_sdk import ACT
act = ACT(api_key=os.getenv("ACT_API_KEY"))
def create_agent_session(agent_id, customer_id, shift):
"""Issue a capability token for an agent session."""
token = act.issue_token(
agent=agent_id,
policy="customer-support-standard",
context={
"session.customer_id": customer_id,
"agent.id": agent_id,
"shift": shift
},
ttl="8h"
)
return token
Step 3: Validate Actions Against Capabilities
def execute_agent_action(token, action, resource, params):
"""Execute an action only if the agent has the capability."""
validation = act.validate(
token=token,
action=action,
resource=resource,
context=params
)
if validation.allowed:
result = perform_action(action, resource, params)
return {"status": "success", "data": result}
else:
log_capability_violation(token, action, resource, validation)
return {"status": "denied", "reason": validation.reason}
Step 4: Handle Delegation
def delegate_capability(parent_token, child_agent, capabilities, ttl):
"""Delegate specific capabilities to a child agent."""
# ACT ensures delegation can only attenuate, never escalate
child_token = act.delegate(
parent_token=parent_token,
child_agent=child_agent,
capabilities=capabilities,
ttl=ttl # Must be <= parent's remaining TTL
)
# Log the delegation chain
log_delegation(
parent=parent_token.agent_id,
child=child_agent,
capabilities=capabilities,
expires=child_token.expires
)
return child_token
Migration Guide: From RBAC to Capabilities
Phase 1: Audit Existing Roles
# Document what each role actually allows
for role in existing_roles:
print(f"Role: {role.name}")
print(f" Permissions: {role.permissions}")
print(f" Agents using: {role.assigned_agents}")
print(f" Actual usage: {analyze_audit_logs(role)}")
print(f" Over-permissioned: {identify_unused_permissions(role)}")
Phase 2: Create Equivalent Policies
For each role, create ACT policies that match actual usage (not granted permissions):
# Old RBAC role
role: customer_support
permissions: [read_customers, read_orders, create_tickets,
update_tickets, process_refunds, send_emails]
# New ACT policy (based on actual usage analysis)
policy: customer-support-v1
capabilities:
read_customer:
resources: ["customer://id:{{session.customer_id}}"]
constraints: {maxRows: 50}
read_order:
resources: ["order://customer:{{session.customer_id}}/*"]
create_ticket:
resources: ["ticket://agent:{{agent.id}}/*"]
process_refund:
resources: ["order://customer:{{session.customer_id}}/*"]
constraints: {maxAmount: 500, maxPerDay: 5}
send_email:
resources: ["email://domain:@company.com"]
constraints: {maxPerHour: 20}
Phase 3: Deploy in Monitor Mode
Run ACT alongside RBAC without blocking:
# Monitor mode: validate but don't block
validation = act.validate(token, action, resource, context)
if not validation.allowed:
log_would_have_blocked(action, resource, validation.reason)
# Still execute via RBAC
# After 2 weeks, review logs:
# - False positives (legitimate actions that would be blocked)
# - True positives (dangerous actions that should be blocked)
Phase 4: Switch to Enforcement
Once policies are tuned, enable enforcement:
# Enforcement mode: validate and block
validation = act.validate(token, action, resource, context)
if validation.allowed:
execute(action, resource)
else:
block_and_log(action, resource, validation.reason)
Conclusion
RBAC was built for a world of human users with predictable behavior. AI agents live in a different world—one of dynamic actions, autonomous decisions, and context-dependent permissions.
Capability-based security provides what AI agents need:
- ✅ Dynamic permissions that adapt to context
- ✅ Fine-grained control at the action + resource + constraint level
- ✅ Secure delegation with automatic attenuation
- ✅ Granular revocation without affecting other capabilities
- ✅ Complete audit trails for every capability used
The transition from RBAC to capabilities isn't just a security upgrade—it's a fundamental shift that makes AI agents governable, auditable, and safe for production.
Implement capability-based security for your AI agents Get Started with ACT →
Related articles: