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.
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.
When a manager delegates to a worker, the worker inherits tool access without scoping. Delegation becomes a privilege escalation vector.
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.
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.
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
Install the SDK
pip install veto-sdk crewaiDefine role-based policies
Create veto/policies.yaml with rules per agent role. Use context.agent_role to scope access.
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?
Can different agents have different policies for the same tool?
Does it work with hierarchical and sequential processes?
What happens when a tool call is denied?
Related integrations
Policy for every crew tool call.