Claude + Veto

Runtime authorization for Claude agents built with the Anthropic SDK and Claude Agent SDK. Intercept every tool_use call before execution. Block, approve, or audit. Claude never knows.

The problem with Claude agents today

Claude is built for agentic work. The Anthropic SDK gives it tools, the Agent SDK gives it autonomy, and MCP gives it access to external systems. But none of these provide runtime authorization. Claude decides what to do and your code executes it unconditionally.

This is not hypothetical risk. In February 2026, Check Point disclosed two configuration injection flaws in Claude Code (CVE-2025-59536, severity 8.7/10) that let attackers run arbitrary shell commands by exploiting repository-controlled settings. Anthropic's own sandboxing documentation acknowledges that Claude agents need external containment. Safety training helps. Authorization enforces.

No pre-execution check

The Anthropic SDK's tool_use flow returns tool calls for you to execute. There is no built-in step between "Claude wants to call this" and "your code runs it."

MCP has no auth layer

MCP's authentication is optional. Enterprise deployments using Claude Desktop or Claude Code expose MCP tools with no centralized access control or audit trail.

Prompt injection persists

OWASP LLM01:2025 ranks prompt injection as the top LLM vulnerability. Constitutional AI reduces risk but cannot eliminate it. Authorization is the defense-in-depth layer.

Before and after Veto

The left tab shows a standard Claude tool_use loop. Claude calls tools, your code executes them blindly. The right tab adds Veto. Same agent, same tools, but every call goes through policy evaluation first.

import Anthropic from '@anthropic-ai/sdk'

const client = new Anthropic()

const tools = [
  {
    name: 'send_email',
    description: 'Send an email',
    input_schema: {
      type: 'object',
      properties: {
        to: { type: 'string' },
        subject: { type: 'string' },
        body: { type: 'string' },
      },
      required: ['to', 'subject', 'body'],
    },
  },
  {
    name: 'delete_records',
    description: 'Delete database records',
    input_schema: {
      type: 'object',
      properties: {
        table: { type: 'string' },
        where: { type: 'string' },
      },
      required: ['table', 'where'],
    },
  },
]

const response = await client.messages.create({
  model: 'claude-sonnet-4-6',
  max_tokens: 1024,
  tools,
  messages: [{ role: 'user', content: userMessage }],
})

// Claude decides to call delete_records.
// Your code executes it. No questions asked.
for (const block of response.content) {
  if (block.type === 'tool_use') {
    const result = await executeTool(block.name, block.input)
    // Hope nothing bad happens.
  }
}

Claude Agent SDK (Python)

Anthropic's Claude Agent SDK gives Claude full autonomy with tool definitions, MCP server connections, and multi-turn execution loops. Veto validates inside each tool function, so the agent architecture stays unchanged.

agent_with_veto.pypython
from claude_agent_sdk import Agent, tool
from veto import Veto

veto = Veto(api_key="veto_live_xxx")

@tool
def transfer_funds(from_account: str, to_account: str, amount: float) -> str:
    """Transfer funds between accounts."""
    # Veto validates before the function body runs
    decision = veto.validate(
        tool="transfer_funds",
        arguments={"from_account": from_account, "to_account": to_account, "amount": amount},
        context={"user_role": "agent", "environment": "production"},
    )

    if decision.action != "allow":
        return f"Transfer blocked: {decision.reason}"

    return process_transfer(from_account, to_account, amount)

@tool
def query_database(query: str, table: str) -> str:
    """Execute a database query."""
    decision = veto.validate(
        tool="query_database",
        arguments={"query": query, "table": table},
    )

    if decision.action != "allow":
        return f"Query blocked: {decision.reason}"

    return execute_query(query, table)

agent = Agent(
    model="claude-sonnet-4-6",
    tools=[transfer_funds, query_database],
    instructions="You are a financial operations assistant.",
)

result = agent.run("Transfer $5000 from ops to marketing")

Policy configuration

Define what Claude can and cannot do in declarative YAML. Version control it alongside your code. No prompt engineering. No hoping the model cooperates.

veto/policies.yamlyaml
rules:
  - name: block_destructive_writes
    description: Prevent DELETE/DROP on production tables
    tool: delete_records
    when: context.environment == "production"
    action: deny
    message: "Destructive writes blocked in production"

  - name: approve_large_transfers
    description: Human approval for transfers over $1,000
    tool: transfer_funds
    when: args.amount > 1000
    action: require_approval
    approvers: [finance-team]
    message: "Large transfer requires finance team approval"

  - name: block_external_email
    description: Block emails to non-company domains
    tool: send_email
    when: "!args.to.endsWith('@yourcompany.com')"
    action: deny
    message: "External emails require manual sending"

  - name: read_only_queries
    description: Only allow SELECT queries
    tool: query_database
    when: "!args.query.toUpperCase().startsWith('SELECT')"
    action: deny
    message: "Only read-only queries are permitted"

Quickstart

1

Install

npm install veto-sdk @anthropic-ai/sdk

Python: pip install veto anthropic

2

Define policies

Create veto/policies.yaml with rules for each tool. Match on tool name, constrain arguments, set actions: allow, deny, or require_approval.

3

Validate before executing

Call veto.guard() in your tool_use handler. Check the decision. Execute only if allowed. The model, your prompts, and your agent loop stay untouched.

What Veto covers for Claude agents

Tool scoping

Allowlist which tools Claude can call per environment, user role, or time of day. Deny everything else by default. Principle of least privilege for agents.

Argument constraints

Block specific argument values: email domains, SQL statements containing DELETE, payment amounts above a threshold. Enforce at the data level, not the prompt level.

Human-in-the-loop

Route sensitive tool calls to approval queues. Approvers get Slack or email notifications. The agent pauses until a human responds. One-click allow or deny.

Full audit trail

Every tool call logged with tool name, arguments, decision, reason, timestamp, and user context. Queryable via API. Exportable for SOC2 and compliance.

Frequently asked questions

Can Claude bypass Veto's guardrails?
No. Veto evaluates tool calls in your code, outside the model's context window. Claude cannot modify, reason around, or even detect the policy layer. This is fundamentally different from prompt-based guardrails, which the model can be tricked into ignoring.
Does Veto work with Claude's MCP support?
Yes. Veto provides a managed MCP gateway that intercepts tool calls from any MCP client, including Claude Desktop, Claude Code, and Cursor. Point your MCP client at Veto's gateway URL. All tool calls flow through the policy engine before reaching the upstream server.
Does this add latency to Claude responses?
Policy evaluation typically completes in under 10ms. Negligible compared to Claude's inference time. Human approval workflows pause the agent until a reviewer responds, but standard allow/block decisions are immediate.
Does it work with streaming?
Yes. Veto validates at the tool_use boundary, not on streamed text. When Claude's streaming response includes a tool_use block, Veto evaluates the complete tool call before your code executes it. Text streaming is unaffected.

Related integrations

Stop hoping Claude behaves. Enforce it.