Skip to content
Learn Agentic AI12 min read0 views

RBAC for AI Agents: Role-Based Access Control for Tool Permissions

Implement role-based access control for AI agents to restrict which tools each user can invoke, define permission models, enforce authorization at the tool level, and maintain audit logs for compliance.

Why AI Agents Need RBAC

Traditional RBAC controls which API endpoints a user can access. AI agent RBAC must go further because an agent acts on behalf of the user — and a single agent endpoint might expose dozens of tools with varying sensitivity levels. Without tool-level access control, every user gets access to every tool the agent has, regardless of whether they should.

Consider a customer support agent with tools for looking up orders, processing refunds, accessing customer PII, and modifying account settings. A tier-1 support representative should look up orders and process small refunds. Only supervisors should access PII or modify account settings. The agent needs to enforce these boundaries.

Defining the Permission Model

Start by modeling roles, permissions, and tool mappings:

from dataclasses import dataclass, field
from enum import Enum
from typing import Optional

class Permission(Enum):
    # Read permissions
    READ_ORDERS = "read:orders"
    READ_CUSTOMERS = "read:customers"
    READ_PII = "read:pii"
    READ_ANALYTICS = "read:analytics"

    # Write permissions
    PROCESS_REFUND_SMALL = "write:refund:small"    # Up to $50
    PROCESS_REFUND_LARGE = "write:refund:large"    # Up to $500
    MODIFY_ACCOUNT = "write:account:modify"
    DELETE_ACCOUNT = "write:account:delete"

    # Admin permissions
    ADMIN_VIEW_LOGS = "admin:logs"
    ADMIN_MANAGE_USERS = "admin:users"

@dataclass
class Role:
    name: str
    permissions: set[Permission]
    max_refund_amount: float = 0.0
    description: str = ""

# Define roles
ROLES = {
    "tier1_support": Role(
        name="tier1_support",
        permissions={
            Permission.READ_ORDERS,
            Permission.READ_CUSTOMERS,
            Permission.PROCESS_REFUND_SMALL,
        },
        max_refund_amount=50.0,
        description="Basic support: view orders, small refunds",
    ),
    "tier2_support": Role(
        name="tier2_support",
        permissions={
            Permission.READ_ORDERS,
            Permission.READ_CUSTOMERS,
            Permission.READ_PII,
            Permission.PROCESS_REFUND_SMALL,
            Permission.PROCESS_REFUND_LARGE,
            Permission.MODIFY_ACCOUNT,
        },
        max_refund_amount=500.0,
        description="Senior support: PII access, large refunds, account edits",
    ),
    "admin": Role(
        name="admin",
        permissions=set(Permission),  # All permissions
        max_refund_amount=10000.0,
        description="Full access to all agent tools",
    ),
}

@dataclass
class User:
    id: str
    email: str
    role_name: str

    @property
    def role(self) -> Role:
        return ROLES[self.role_name]

    def has_permission(self, permission: Permission) -> bool:
        return permission in self.role.permissions

Tool Authorization Decorator

A decorator that checks permissions before executing any tool:

import functools
import time
from typing import Callable

class AuthorizationError(Exception):
    def __init__(self, user_id: str, tool_name: str, required_permission: str):
        self.user_id = user_id
        self.tool_name = tool_name
        self.required_permission = required_permission
        super().__init__(
            f"User {user_id} lacks permission {required_permission} for tool {tool_name}"
        )

def require_permission(*permissions: Permission):
    """Decorator that enforces permission checks on agent tools."""
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        async def wrapper(*args, **kwargs):
            # Extract user from the agent context
            context = kwargs.get("context") or (args[0] if args else None)
            if not context or not hasattr(context, "current_user"):
                raise AuthorizationError("unknown", func.__name__, str(permissions))

            user: User = context.current_user
            missing = [p for p in permissions if not user.has_permission(p)]

            if missing:
                # Log the denied access attempt
                audit_logger.log_denied(
                    user_id=user.id,
                    tool=func.__name__,
                    required=str(missing),
                )
                raise AuthorizationError(
                    user.id, func.__name__, str(missing)
                )

            # Log the authorized access
            audit_logger.log_allowed(
                user_id=user.id,
                tool=func.__name__,
            )

            return await func(*args, **kwargs)
        wrapper._required_permissions = permissions
        return wrapper
    return decorator

Applying RBAC to Agent Tools

@require_permission(Permission.READ_ORDERS)
async def lookup_order(context, order_id: str) -> dict:
    """Look up an order by ID."""
    return await db.orders.find_one({"id": order_id})

@require_permission(Permission.READ_PII)
async def get_customer_pii(context, customer_id: str) -> dict:
    """Retrieve customer personal information including address and phone."""
    return await db.customers.find_one(
        {"id": customer_id},
        projection={"name": 1, "email": 1, "phone": 1, "address": 1},
    )

@require_permission(Permission.PROCESS_REFUND_SMALL, Permission.PROCESS_REFUND_LARGE)
async def process_refund(context, order_id: str, amount: float, reason: str) -> dict:
    """Process a refund for an order."""
    user: User = context.current_user
    max_allowed = user.role.max_refund_amount

    if amount > max_allowed:
        return {
            "error": f"Refund amount ${amount} exceeds your limit of ${max_allowed}. "
                     "Please escalate to a supervisor."
        }

    return await payment_service.refund(order_id, amount, reason, approved_by=user.id)

Dynamic Tool Filtering

Instead of exposing all tools and checking permissions at call time, filter the tool list before the agent sees it:

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

class RBACToolFilter:
    """Filter available tools based on user permissions."""

    def __init__(self, all_tools: list):
        self.all_tools = all_tools

    def get_tools_for_user(self, user: User) -> list:
        """Return only tools the user has permission to use."""
        authorized_tools = []

        for tool in self.all_tools:
            func = tool.func if hasattr(tool, "func") else tool
            required = getattr(func, "_required_permissions", None)

            if required is None:
                authorized_tools.append(tool)
                continue

            if all(user.has_permission(p) for p in required):
                authorized_tools.append(tool)

        return authorized_tools

# Usage with the OpenAI Agents SDK
from agents import Agent

def create_agent_for_user(user: User) -> Agent:
    tool_filter = RBACToolFilter(all_tools=[
        lookup_order,
        get_customer_pii,
        process_refund,
    ])

    authorized_tools = tool_filter.get_tools_for_user(user)

    return Agent(
        name="Support Agent",
        instructions=f"You are helping {user.email} (role: {user.role_name}). "
                     f"You only have access to the tools listed below.",
        tools=authorized_tools,
    )

Audit Logging

Every tool invocation — whether allowed or denied — must be logged for compliance:

import json
from datetime import datetime, timezone

class AuditLogger:
    def __init__(self, log_store):
        self.store = log_store

    def _log(self, event_type: str, **kwargs) -> None:
        entry = {
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "event_type": event_type,
            **kwargs,
        }
        self.store.append(json.dumps(entry))

    def log_allowed(self, user_id: str, tool: str, **extra) -> None:
        self._log("tool_access_granted", user_id=user_id, tool=tool, **extra)

    def log_denied(self, user_id: str, tool: str, required: str, **extra) -> None:
        self._log(
            "tool_access_denied",
            user_id=user_id,
            tool=tool,
            required_permissions=required,
            **extra,
        )

    def log_tool_result(self, user_id: str, tool: str, success: bool, **extra) -> None:
        self._log(
            "tool_execution",
            user_id=user_id,
            tool=tool,
            success=success,
            **extra,
        )

audit_logger = AuditLogger(log_store=[])

FAQ

Should the agent know about tools it cannot use?

No. Filter tools before passing them to the agent, not just at execution time. If the agent knows about a tool but cannot use it, it may still try to use it or mention it to the user, creating confusion. When the agent only sees authorized tools, it naturally limits its suggestions and actions to what the user can actually do.

How do I handle permission escalation during a conversation?

Implement a handoff pattern. When the agent encounters an action requiring higher permissions, it should inform the user and offer to escalate to a supervisor. In practice, this means switching to a new agent instance configured with the supervisor's tools, or queuing the action for supervisor approval. Never temporarily grant elevated permissions — this defeats the purpose of RBAC.

How granular should tool permissions be?

Match the granularity to your risk model. Read-only tools with non-sensitive data can share a single permission. Write tools that modify data or trigger external actions should each have their own permission. Tools accessing PII or financial data should have fine-grained permissions that distinguish between viewing and modifying. Start with coarse permissions and refine as you discover edge cases in production.


#RBAC #Authorization #AISafety #AccessControl #Python #AgenticAI #LearnAI #AIEngineering

Share this article
C

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.