YAML Rule Format
Complete reference for Veto's YAML rule syntax.
Each policy file (e.g. veto/rules/policy.yaml) can contain:
extendsto inherit from a built-in policy packrulesfor pre-execution input validationoutput_rulesfor post-execution output validation/redaction
For end-to-end examples, see Output Validation & Redaction. For business-hour policies, see Time-Based Conditions. For built-in packs, see Policy Packs.
Full schema
version: "1.0" # [Required] Policy schema version
extends: "@veto/coding-agent" # [Optional] Inherit built-in pack rules
rules:
- id: unique-rule-id # [Required] Unique identifier
name: Human readable name # [Required] Descriptive name for logging
enabled: true # [Optional] Default: true
severity: high # [Optional] critical, high, medium, low, info
action: block # [Required] block, warn, log, allow, require_approval
# Scope: which tools does this rule apply to?
tools: # [Optional] List of tool names
- make_payment # If omitted, applies to ALL tools
# Optional agent scope:
# - list form: include-only
# - object form: exclude listed agents
agents:
- support-agent
- ops-agent
# OR
# agents:
# not:
# - internal-auditor
# Static conditions (optional):
# Evaluated locally before LLM validation
conditions:
- field: arguments.amount # Dot notation for nested args
operator: greater_than # See operators table below
value: 1000
- field: context.time
operator: outside_hours
value:
start: "09:00"
end: "17:00"
timezone: "America/New_York"
days: ["mon", "tue", "wed", "thu", "fri"]
# Condition groups (optional):
# OR-of-ANDs — use instead of `conditions` for compound logic
condition_groups:
- - field: arguments.amount
operator: greater_than
value: 10000
- - field: arguments.currency
operator: not_in
value: ["USD", "EUR"]
- field: arguments.amount
operator: greater_than
value: 1000
# Cross-tool sequence constraints (optional):
# Evaluate prior calls in this session history.
blocked_by:
- tool: read_file
conditions:
- field: arguments.path
operator: starts_with
value: "/etc/secrets"
requires:
- tool: verify_identity
within: 300 # Optional time window in seconds
conditions:
- field: arguments.level
operator: equals
value: strong
# Description (optional):
# Natural language guidance for the validation LLM
description: "Ensure the payment recipient is a verified vendor."
output_rules:
- id: unique-output-rule-id # [Required] Unique identifier
name: Human readable name # [Required] Descriptive name
enabled: true # [Optional] Default: true
severity: high # [Optional] critical, high, medium, low, info
action: redact # [Required] block, redact, log
# Scope: which tools does this output rule apply to?
tools:
- make_payment # If omitted, applies to ALL tools
# Output conditions (optional):
# Same operators as input conditions
output_conditions:
- field: output.receipt.email
operator: matches
value: "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
# Output condition groups (optional):
# OR-of-ANDs for output checks
output_condition_groups:
- - field: output.secret
operator: contains
value: token
- - field: output.card
operator: matches
value: "\\d{16}"
# Replacement text for redact action
redact_with: "[REDACTED]"
description: "Prevent sensitive data from leaving the tool boundary."Policy file fields
| Field | Required | Description |
|---|---|---|
version | Yes | Schema version. Use "1.0" |
extends | No | Built-in pack name to inherit from (for example @veto/coding-agent) |
rules | No* | Input validation rules |
output_rules | No* | Output validation and redaction rules |
* A policy file must define at least one of: extends, rules, or output_rules.
When extends is used, Veto loads the pack first, then merges your rules on top:
- Same rule ID: your rule fully overrides the pack rule
- New rule ID: your rule is appended
Input rule fields (rules)
| Field | Required | Default | Description |
|---|---|---|---|
id | Yes | — | Unique identifier for the rule |
name | Yes | — | Human-readable name for logging |
enabled | No | true | Whether the rule is active |
severity | No | medium | critical, high, medium, low, info |
action | Yes | — | block, warn, log, allow, require_approval |
tools | No | All tools | List of tool names this rule applies to |
agents | No | All agents | Agent scope for local matching: include list ([a,b]) or exclusion ({ not: [a,b] }) |
conditions | No | — | Static constraint checks (AND logic) |
condition_groups | No | — | Compound constraint checks (OR-of-ANDs logic) |
blocked_by | No | — | Sequence constraint(s): trigger when any listed prior call exists |
requires | No | — | Sequence constraint(s): trigger when any listed prerequisite call is missing |
description | No | — | Natural language guidance for LLM validation |
Output rule fields (output_rules)
| Field | Required | Default | Description |
|---|---|---|---|
id | Yes | — | Unique identifier for the output rule |
name | Yes | — | Human-readable output rule name |
enabled | No | true | Whether the output rule is active |
severity | No | medium | critical, high, medium, low, info |
action | Yes | — | block, redact, log |
tools | No | All tools | List of tool names this output rule applies to |
output_conditions | No | — | Output checks (AND logic) |
output_condition_groups | No | — | Output checks (OR-of-ANDs logic) |
redact_with | No | [REDACTED] | Replacement text used for redact |
description | No | — | Human-readable reason (used in logs/block reason) |
Condition operators
These operators apply to both conditions and output_conditions.
| Operator | Type | Description | Example |
|---|---|---|---|
equals | any | Exact match | value: "admin" |
not_equals | any | Must not equal | value: "admin" |
contains | string | Substring match | value: "password" |
not_contains | string | Must NOT contain | value: "secret" |
starts_with | string | Prefix match | value: "/etc" |
ends_with | string | Suffix match | value: ".exe" |
matches | string | Regex pattern (max 256 chars, ReDoS-safe) | value: "^[^@]+@company\\.com$" |
in | string/number | Value in allowlist | value: ["USD", "EUR"] |
not_in | string/number | Value NOT in denylist | value: ["admin", "root"] |
greater_than | number | Numeric > comparison | value: 1000 |
less_than | number | Numeric < comparison | value: 0 |
within_hours | object | Time is inside window | value: { start: "09:00", end: "17:00", timezone: "UTC" } |
outside_hours | object | Time is outside window | value: { start: "09:00", end: "17:00", timezone: "UTC" } |
For within_hours and outside_hours, value must include:
start(HH:MM, 24-hour)end(HH:MM, 24-hour)timezone(IANA timezone, e.g.America/New_York)- optional
daysarray (mon,tue,wed,thu,fri,sat,sun)
Virtual context fields
Conditions can reference runtime context fields (not just tool arguments):
| Field | Type | Description |
|---|---|---|
context.time | string | Current timestamp (ISO string), typically used with within_hours / outside_hours |
context.day_of_week | string | Current lowercase day abbreviation (sun..sat) |
Example weekend lock rule:
rules:
- id: weekend-lockdown
name: Restrict prod deploys on weekends
action: block
tools: [deploy]
conditions:
- field: context.day_of_week
operator: in
value: ["sat", "sun"]Condition groups (OR logic)
When you need compound logic beyond simple AND, use condition_groups instead of conditions. Each group is an array of conditions joined with AND, and groups are joined with OR.
Only use one of conditions or condition_groups per rule. If both are set, conditions takes precedence.
rules:
- id: restrict-high-risk-transfers
name: Block high-risk financial transfers
action: block
tools:
- transfer_funds
condition_groups:
# Group 1: Large transfers to any destination
- - field: arguments.amount
operator: greater_than
value: 50000
# Group 2: Any transfer to a non-approved currency AND amount > 1000
- - field: arguments.currency
operator: not_in
value: ["USD", "EUR", "GBP"]
- field: arguments.amount
operator: greater_than
value: 1000This rule triggers if either group matches:
- Amount exceeds 50,000 (any currency), OR
- Amount exceeds 1,000 in a non-approved currency
Sequence constraints (blocked_by, requires)
Use sequence constraints when rule decisions depend on earlier tool calls in the same session history.
blocked_by: rule triggers if any listed historical call matches.requires: rule triggers if any listed requirement is not found.- Historical
conditions/condition_groupsare evaluated against the prior call context (usearguments.*fields). withinis optional and measured in seconds from the current call time.- Only retained history entries are checked (default history size is 100).
rules:
- id: prevent-data-exfiltration
name: Block send after sensitive read
action: block
tools: [send_email, upload_file]
blocked_by:
- tool: read_file
conditions:
- field: arguments.path
operator: starts_with
value: "/etc/secrets"
- id: require-auth-before-transfer
name: Require recent verification
action: block
tools: [transfer_funds]
requires:
- tool: verify_identity
within: 300Sequence constraints are currently enforced in local YAML validation mode.
Agent scoping (agents)
Use agents when a rule should only apply for certain agent identities.
Include-only
rules:
- id: scoped-rule
name: Applies only to selected agents
action: block
tools: [deploy]
agents:
- deploy-bot
- ci-agentExclusion
rules:
- id: excluded-rule
name: Applies to everyone except excluded agents
action: require_approval
tools: [transfer_funds]
agents:
not:
- internal-auditorMatching rules:
- no
agentsfield: applies to all agents agents: [a, b]: applies only foraorbagents: { not: [a, b] }: applies for everyone exceptaorb
Input rule matching logic
1. Rule selection
Veto selects rules based on tools, then applies any agents scope:
- Tool-specific rules: If a rule lists specific tools (e.g.
tools: [make_payment]), it only applies when those tools are called - Global rules: If
toolsis missing or empty[], the rule activates for every tool call - Agent-scoped rules: If
agentsis present, the rule is further filtered by current agent identity before evaluating conditions
2. Validation execution
For each intercepted tool call, Veto aggregates all applicable rules (global + specific) and validates:
- Static conditions — if
conditionsare defined, they're checked first. If a condition matches, the rule triggers immediately - Semantic validation — if no static conditions match (or none exist), the rule's
nameanddescriptionare sent to the LLM for semantic evaluation
Output rule matching logic
For each executed tool result, Veto aggregates all applicable output rules (global + specific) and evaluates output_conditions / output_condition_groups.
Action resolution order:
- If any matched output rule has
action: block, the output is rejected. - Otherwise, matched
action: redactrules apply regex replacements usingredact_with. - Matched
action: logrules emit warnings and pass output through.
When block and redact both match, block takes precedence.
Examples
Block large financial transfers
rules:
- id: limit-transfers
name: Limit large transfers
action: block
severity: critical
tools:
- transfer_funds
- send_payment
conditions:
- field: arguments.amount
operator: greater_than
value: 10000Prevent file access outside project
rules:
- id: restrict-file-paths
name: Restrict file access to project directory
action: block
severity: high
tools:
- read_file
- write_file
conditions:
- field: arguments.path
operator: starts_with
value: "/etc"Global policy via LLM
rules:
- id: no-pii-disclosure
name: Prevent PII disclosure
action: block
severity: critical
description: >
Block any tool call that would expose personally identifiable
information such as social security numbers, credit card numbers,
or home addresses to external services.