CrewAI runtime authorization

Wrap CrewAI crew roles with Veto. Each governed crew-role tool call is evaluated before dispatch: allow, review, or deny, with an exportable decision record per governed decision.

The problem with CrewAI tool delegation

CrewAI orchestrates teams of specialized agents with distinct roles. But "role" is a prompt concept, not an enforcement mechanism. An agent can call tools it is given access to, and delegation lets a manager hand tasks to workers without scoping which tools the delegated agent can use. There is no runtime check that the Researcher stays read-only or the Writer has no payment-tool access.

The security risk is architectural. Code interpreters, RAG search tools, file loaders, and delegated workers all become executable surfaces once the model can choose tools and arguments. Prompt injection does not need to break CrewAI itself; it only needs to steer a worker toward a tool your runtime should have scoped or blocked.

No role enforcement

Agent "roles" are prompt strings, not access control. A Researcher agent with access to a delete tool can use it if the LLM decides to. Roles do not constrain tools.

Delegation escalation

When a manager delegates to a worker, the worker inherits tool access without scoping. Delegation becomes a privilege escalation vector.

Executable tool surface

Code execution, retrieval, file loading, and internal service access all need runtime policy before the worker executes the tool.

Before and after Veto

The left tab shows a standard CrewAI setup. Agents have tools but no enforcement. The right tab adds Veto with role-based policies inside each tool function.

py
import os
from crewai import Agent, Task, Crew, Process
from crewai.tools import tool

@tool
def read_documents(query: str) -> str:
    """Search and read documents from the knowledge base."""
    return document_store.search(query)

@tool
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email to a recipient."""
    return email_service.send(to, subject, body)

@tool
def delete_records(table: str, condition: str) -> str:
    """Delete records from a database table."""
    return db.execute(f"DELETE FROM {table} WHERE {condition}")

@tool
def process_payment(amount: float, recipient: str) -> str:
    """Process a payment transaction."""
    return payment_service.charge(amount, recipient)

researcher = Agent(
    role="Researcher",
    goal="Find and analyze relevant information",
    tools=[read_documents],
)

writer = Agent(
    role="Writer",
    goal="Create content based on research",
    tools=[send_email],
)

manager = Agent(
    role="Manager",
    goal="Oversee operations and approve decisions",
    tools=[delete_records, process_payment],
    allow_delegation=True,
)

# Every agent can call its tools without guardrails.
# The Manager can delegate to Writer or Researcher.
# Role prompts do not prevent privilege escalation through delegation.
# CrewAI's code interpreter silently falls back to an
# insecure sandbox when Docker is unavailable.
crew = Crew(
    agents=[researcher, writer, manager],
    tasks=[research_task, writer_task],
    process=Process.hierarchical,
    manager_agent=manager,
)

result = crew.kickoff()

Role-based policy configuration

Define what each agent role can do. Researcher gets read-only. Writer can send internal emails with approval for external. Manager gets write access with payment limits. Roles are enforced at the tool call boundary, not in prompts.

veto/policies.yaml
rules:
  # Researcher: read-only access
  - name: researcher_read_only
    description: Researcher can only read documents
    tool: read_documents
    when: context.agent_role == "researcher"
    action: allow

  - name: researcher_no_write
    description: Block researcher from write operations
    tool: [send_email, delete_records, process_payment]
    when: context.agent_role == "researcher"
    action: deny
    message: "Researcher role has read-only access"

  # Writer: can send email, but not to external domains
  - name: writer_internal_email_only
    description: Writer can only email internal addresses
    tool: send_email
    when: context.agent_role == "writer" && !args.to.endswith("@approved.example")
    action: require_approval
    approvers: [content-lead]
    message: "External emails from writer require content lead approval"

  # Manager: full access with limits
  - name: manager_block_production_deletes
    description: Block destructive writes in production
    tool: delete_records
    when: context.environment == "production"
    action: deny
    message: "Destructive operations blocked in production"

  - name: manager_approve_large_payments
    description: Payments over $1,000 need CFO approval
    tool: process_payment
    when: args.amount > 1000
    action: require_approval
    approvers: [cfo]
    timeout: 1h

  - name: manager_block_huge_payments
    description: Hard block on payments over $50,000
    tool: process_payment
    when: args.amount > 50000
    action: deny
    message: "Payments over $50,000 require manual processing"

First governed call

1

Install the SDK

pip install veto-sdk crewai
2

Define role-based policies

Create veto/policies.yaml with rules per agent role. Use context.agent_role to scope access.

3

Add veto.guard() to each tool

Call veto.guard() with the agent's role as context. Policy stays inside tool functions rather than the crew planner.

What Veto covers for CrewAI agents

Role-based tool access

Enforce that each agent role can only use specific tools. Researcher: read-only. Writer: email with approval. Manager: payments with limits. Enforced in code, not prompts.

Delegation protection

When a manager delegates, the worker's tool calls still go through Veto with the worker's role context. No privilege escalation through delegation chains.

Human review

Route review-required tool calls to human approval queues per role. Writers need content lead approval for external emails. Managers need CFO approval for large payments.

Decision record

Governed tool calls can log agent role, tool name, arguments, decision, and timestamp. See which agent in which crew attempted what, and what was allowed.

Frequently asked questions

How does Veto handle agent delegation in CrewAI?
When a manager delegates a task to a worker, the worker's tool calls go through Veto with the worker's role context, not the manager's. Policies evaluate based on the executing agent's role, preventing privilege escalation through the delegation chain.
Can different agents have different policies for the same tool?
Yes. Policies condition on context.agent_role. The same send_email tool can allow internal emails for writers but require approval for managers sending to external domains. Each rule specifies which roles it applies to.
Does it work with hierarchical and sequential processes?
Yes. Veto validates inside tool functions, so it works regardless of CrewAI's process type: sequential, hierarchical, or consensual. Policy stays inside tool functions rather than crew planning.
What happens when a tool call is denied?
The tool function returns a string explaining the denial. The agent sees this as a normal tool response and can adjust its approach. All denials are recorded with decision context for audit.

Related integrations

Policy for every crew tool call.