Documentation Index
Fetch the complete documentation index at: https://mintlify.com/plawio/veto/llms.txt
Use this file to discover all available pages before exploring further.
Integrate Veto into your CI/CD pipeline to enforce policy coverage, catch rule errors, and prevent uncovered tools from reaching production.
Why CI/CD Integration?
Prevent Uncovered Tools
Fail builds if new tools are added without rules.
Catch Rule Errors
Validate YAML syntax and rule logic before deployment.
Policy Diff in PRs
Show what rules changed and their impact on historical calls.
Enforce Standards
Require tests and coverage thresholds for all policy changes.
Using veto scan in CI
The veto scan command discovers tools in your codebase and checks if they have rules.
Basic Scan
Output:
Veto Scan Coverage Audit
========================
Project directory: /home/user/my-agent
Rules loaded: 5 (global: 0)
Framework hints: langchain
Coverage: 4/5 (80.0%)
Discovered tools:
[COVERED] transfer_funds(amount, from_account, to_account)
locations: src/tools/financial.ts
sources: source-ts
[COVERED] get_balance(account_id)
locations: src/tools/financial.ts
sources: source-ts
[UNCOVERED] send_email(to, subject, body)
locations: src/tools/communication.ts
sources: source-ts
Use --fail-uncovered to exit with code 1 if any tools lack rules:
veto scan --fail-uncovered
Exit code:
0: All tools covered
1: Uncovered tools found
JSON Output
For programmatic parsing:
veto scan --format json > coverage-report.json
Output:
{
"timestamp": "2026-03-04T14:23:45.123Z",
"projectDir": "/home/user/my-agent",
"summary": {
"total": 5,
"covered": 4,
"uncovered": 1,
"coveragePercent": 80.0
},
"discoveredTools": [
{
"name": "transfer_funds",
"covered": true,
"coverageReason": "tool-rule",
"matchedRuleIds": ["block-large-transfers", "block-external-transfers"]
},
{
"name": "send_email",
"covered": false,
"coverageReason": "none",
"matchedRuleIds": []
}
]
}
Generate Suggestions
Get starter rule snippets for uncovered tools:
Output:
Suggested starter rules:
send_email (@veto/communication)
Rationale: Tool name matches communication keywords (e.g. email/message/notify).
Snippet:
rules:
- id: guard-send-email
name: Guard send_email
description: Restrict sensitive outbound communication
enabled: true
severity: high
action: block
tools:
- send_email
conditions:
- field: arguments.to
operator: not_contains
value: '@company.com'
GitHub Actions
Example Workflow
.github/workflows/veto-ci.yml
name: Veto Policy CI
on:
pull_request:
paths:
- 'veto/**'
- 'src/**'
push:
branches: [main]
jobs:
policy-check:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Veto CLI
run: npm install -g veto-cli
- name: Scan for uncovered tools
run: veto scan --fail-uncovered
- name: Run policy tests
run: npm test -- test-policies.test.ts
- name: Adversarial analysis
run: veto test --policy ./veto/rules
- name: Generate policy diff
if: github.event_name == 'pull_request'
run: |
git fetch origin main
veto diff --old origin/main:veto/rules --new HEAD:veto/rules > policy-diff.txt
cat policy-diff.txt >> $GITHUB_STEP_SUMMARY
.github/workflows/veto-pr-comment.yml
name: Veto Coverage Comment
on:
pull_request:
paths:
- 'veto/**'
- 'src/**'
jobs:
coverage-comment:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Veto CLI
run: npm install -g veto-cli
- name: Run coverage scan
id: scan
run: |
veto scan --format json > coverage.json
COVERAGE=$(jq -r '.summary.coveragePercent' coverage.json)
UNCOVERED=$(jq -r '.summary.uncovered' coverage.json)
echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT
echo "uncovered=$UNCOVERED" >> $GITHUB_OUTPUT
- name: Comment on PR
uses: actions/github-script@v7
with:
script: |
const coverage = '${{ steps.scan.outputs.coverage }}';
const uncovered = '${{ steps.scan.outputs.uncovered }}';
const body = `## Veto Policy Coverage\n\n` +
`Coverage: **${coverage}%**\n\n` +
(uncovered > 0
? `⚠️ **${uncovered} uncovered tool(s) detected**\n\nRun \`veto scan\` locally for details.`
: `✅ All tools covered by rules.`);
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body,
});
GitLab CI
Example Pipeline
stages:
- test
- policy
veto:scan:
stage: policy
image: node:20
script:
- npm install -g veto-cli
- veto scan --fail-uncovered
only:
changes:
- veto/**
- src/**
veto:test:
stage: policy
image: node:20
script:
- npm install -g veto-cli
- veto test --policy ./veto/rules --format json > gaps.json
- |
CRITICAL=$(jq '.summary.critical' gaps.json)
if [ "$CRITICAL" -gt 0 ]; then
echo "Critical policy gaps found!"
exit 1
fi
artifacts:
reports:
junit: gaps.json
veto:diff:
stage: policy
image: node:20
script:
- npm install -g veto-cli
- git fetch origin main
- veto diff --old origin/main:veto/rules --new HEAD:veto/rules > diff.txt
artifacts:
paths:
- diff.txt
only:
- merge_requests
CircleCI
Example Config
version: 2.1
jobs:
veto-scan:
docker:
- image: cimg/node:20.0
steps:
- checkout
- run:
name: Install Veto CLI
command: npm install -g veto-cli
- run:
name: Scan for uncovered tools
command: veto scan --fail-uncovered
- run:
name: Run policy tests
command: npm test -- test-policies.test.ts
workflows:
test:
jobs:
- veto-scan:
filters:
branches:
only:
- main
- develop
Policy Diff in PRs
Show what changed between policy versions with veto diff:
veto diff --old origin/main:veto/rules --new HEAD:veto/rules
Output:
Veto Policy Diff
================
Old: origin/main:veto/rules
New: HEAD:veto/rules
Structural Changes:
Added rules: 1
Removed rules: 0
Modified rules: 2
--- ADDED ---
[+] require-approval-for-prod-deploy
Scope: deploy
Action: require_approval
Summary: Gate production deployments with human approval
--- MODIFIED ---
[~] block-large-transfers
Scope: transfer_funds
Changes:
- action: block → require_approval
- conditions[0].value: 10000 → 5000
With Impact Analysis
Replay historical calls to see how the diff affects them:
veto diff --old origin/main:veto/rules --new HEAD:veto/rules --log audit.jsonl
Output:
Impact Report:
Total calls: 100
Changed decisions: 12 (12.0%)
- allow → deny: 3
- allow → require_approval: 7
- deny → allow: 2
Samples:
[Line 45] transfer_funds: allow → deny
Old: No rule matched
New: block-large-transfers (threshold lowered from $10,000 to $5,000)
[Line 67] deploy: allow → require_approval
Old: No rule matched
New: require-approval-for-prod-deploy
Use this to validate policy changes before merging.
Enforcing Coverage Thresholds
Require 100% Coverage
.github/workflows/veto-ci.yml
- name: Enforce 100% coverage
run: |
COVERAGE=$(veto scan --format json | jq -r '.summary.coveragePercent')
if [ "$COVERAGE" != "100.0" ]; then
echo "Coverage is $COVERAGE%, but 100% is required"
exit 1
fi
Require Minimum Coverage
- name: Enforce 80% coverage
run: |
COVERAGE=$(veto scan --format json | jq -r '.summary.coveragePercent')
THRESHOLD=80
if (( $(echo "$COVERAGE < $THRESHOLD" | bc -l) )); then
echo "Coverage is $COVERAGE%, but $THRESHOLD% is required"
exit 1
fi
Preventing Regression
Block if Coverage Drops
- name: Check coverage didn't drop
run: |
git fetch origin main
git checkout origin/main
OLD_COVERAGE=$(veto scan --format json | jq -r '.summary.coveragePercent')
git checkout -
NEW_COVERAGE=$(veto scan --format json | jq -r '.summary.coveragePercent')
if (( $(echo "$NEW_COVERAGE < $OLD_COVERAGE" | bc -l) )); then
echo "Coverage dropped from $OLD_COVERAGE% to $NEW_COVERAGE%"
exit 1
fi
Docker Integration
Run Veto in a Docker container:
FROM node:20-alpine
RUN npm install -g veto-cli
WORKDIR /workspace
ENTRYPOINT ["veto"]
docker build -f Dockerfile.veto -t veto:latest .
docker run -v $(pwd):/workspace veto:latest scan --fail-uncovered
Pre-Commit Hook
Add a pre-commit hook to validate policies locally:
#!/bin/bash
if git diff --cached --name-only | grep -q 'veto/'; then
echo "Validating Veto policies..."
veto scan --quiet
if [ $? -ne 0 ]; then
echo "Policy validation failed. Fix errors before committing."
exit 1
fi
fi
chmod +x .git/hooks/pre-commit
Real-World Example: Complete CI Pipeline
Install Veto CLI
- name: Install Veto CLI
run: npm install -g veto-cli
Scan for uncovered tools
- name: Scan coverage
run: veto scan --fail-uncovered
Run policy tests
- name: Test policies
run: npm test -- test-policies.test.ts
Adversarial analysis
- name: Find policy gaps
run: veto test --policy ./veto/rules
Generate diff for PR
- name: Policy diff
if: github.event_name == 'pull_request'
run: |
git fetch origin main
veto diff --old origin/main:veto/rules --new HEAD:veto/rules > diff.txt
cat diff.txt >> $GITHUB_STEP_SUMMARY
Best Practices
Fail Fast
Run veto scan --fail-uncovered early in the pipeline.- name: Scan
run: veto scan --fail-uncovered
Test on Every PR
Validate policies whenever code or rules change.on:
pull_request:
paths:
- 'veto/**'
- 'src/**'
Cache Dependencies
Speed up CI by caching Veto CLI installation.- uses: actions/cache@v4
with:
path: ~/.npm
key: veto-${{ runner.os }}
Store Reports
Archive coverage reports for historical tracking.- uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage.json
Troubleshooting
”veto: command not found”
Ensure Veto CLI is installed:
npm install -g veto-cli
veto --version
“No rules loaded”
Verify veto/veto.config.yaml exists and points to the correct rules directory:
rules:
directory: "./rules"
recursive: true
Veto scans TypeScript/JavaScript/Python source files. Check:
- Are tools defined in supported languages?
- Are tool files in excluded directories (e.g.,
node_modules, dist)?
Use veto scan --include-examples --include-tests to scan all directories.
Next Steps
Testing Policies
Write tests for your rules
Writing Rules
Learn all rule syntax and operators
Audit Trail
Export decisions for compliance
Approval Workflows
Set up human-in-the-loop approval