Rule Engine
Warden ships with hundreds of compiled patterns organized into categories:
| Category | Examples |
|---|---|
| Safety | Block rm -rf, sudo, chmod 777, history rewrites (git filter-branch), credential file writes, reading or uploading SSH keys and cloud credentials |
| Destructive | Block work-destroying operations that are not undoable — see below |
| Substitution | Steer grep → rg, find → fd, curl → xh, cat → bat |
| Hallucination | Detect invented CLI flags, non-existent subcommands, wrong argument patterns |
| Path protection | Prevent writes to .env, credentials, system files, lock files |
| Chain | Block relationships between commands — a download piped into a shell, the environment piped to the network |
| Git policy | Block all mutating git commands (add, commit, push, merge, rebase) — off unless you set git_readonly = true |
| Advisory | Suggest better approaches, warn about large file reads, flag verification debt |
Rules are compiled into the binary — no runtime parsing, no config files to maintain. New rules ship with every release. Run warden --debug restrictions list to see every rule ID with its category and severity, or warden --debug describe --all for the raw pattern dump.
How Rules Work
All patterns are evaluated simultaneously using a compiled regex set. The evaluation cost doesn’t grow linearly with the number of rules — a single pass checks everything.
Rules are available the instant Warden loads. They can’t go missing, get corrupted, or fail to download. When you update Warden, you get the exact rule set tested against that version.
You can extend the built-in rules with TOML overrides (see rules.toml), but the compiled floor is always present.
Rule Categories in Detail
Safety — universally dangerous operations that are always blocked:
rm -rfon broad paths (~,/,.,*)- Privilege escalation (
sudo,su -,doas,runas) - Dangerous permissions (
chmod 777,chmod -R 777,chmod a+w) - System damage (
mkfs,dd if=,format C:,diskpart) - Process killing (
kill -9 1,killall,pkill -9) - Environment destruction (
export PATH=,unset PATH) - Reading credential material into the transcript (
cat ~/.ssh/id_rsa,~/.aws/credentials,.netrc) or uploading it with a data flag
Destructive — always on, and distinct from the opt-in git policy below. An agent committing and pushing its work is ordinary; an agent destroying work that was never committed is a different risk class, and only the second is blocked by default:
git push --force— use--force-with-lease, and ask firstgit reset --hard— discards uncommitted workgit clean -f— deletes untracked files irreversiblygit checkout ./git restore .— discards every uncommitted change at once- Tools that auto-modify code (
knip --fix,sg -r,madge --image)
Rehearsals are exempt: --dry-run, -n, and --what-if reach the real command untouched.
Chain — patterns whose subject spans a pipe or &&, matched against the raw command before it is split:
- A download piped into a shell or an interpreter that executes the stream
- The environment or a credential file piped to a network tool
- Download-then-execute and download-then-
chmod +xsequences - A fork bomb, and redirects onto a raw block device
Piping a download into an interpreter that is only parsing it — xh GET … | python -c "json.load(sys.stdin)" — is ordinary work and is not blocked. That distinction was measured: on 13,162 real commands it was the difference between a 0.099% and a 0.038% false-block rate.
Substitution — steers legacy tools toward modern alternatives. Most are advisory: the command runs and the agent is told what to use next time. Only three are rewritten before execution.
Rewritten in place:
lstoeza— better directory listingsdutodust— visual disk usage treets-nodetotsx— for plain invocations; calls with ts-node-specific flags get an advisory instead
Advisory (the command still runs):
greptorg(ripgrep) — faster search that respects.gitignore, so less output reaches the context windowfindtofd— faster file discovery with sane defaultscurltoxh— friendlier HTTP client with colored outputcattobat— syntax highlighting and line numberstar/zip/unzip/gziptoouch— auto-detecting archive toolsort | uniqtohuniq— preserves insertion order
Blocked outright:
sdon Windows — mangles newlines, use the Edit tool instead
A rule is skipped entirely when its target tool isn’t installed — rewriting to a missing binary breaks the command, and teaching one is noise.
Hallucination — catches commands the agent fabricated:
- URL-encoded path traversal (
%2e%2e/) - Null byte injection (
\x00,%00) - Reverse shell patterns (
/dev/tcp/,socat EXEC:,ncat -e) - Credential exfiltration (piping
.ssh/id_rsaor.envtocurl/wget) - Command hijacking (
alias sudo=...,eval $(curl ...)) - Base64-decoded command execution
Path protection — prevents writes to sensitive locations:
- SSH keys and config (
~/.ssh/) - GPG keys (
~/.gnupg/) - Cloud credentials (
~/.aws/credentials,~/.azure/,~/.kube/config,~/.gcloud/) - Docker credentials (
~/.docker/config.json) - System directories (
/etc/,/usr/,C:\Windows,C:\Program Files) - Certificate and key files (
.pem,.key,.p12,.pfx) - Terraform state (
~/.terraform/)
Git policy — the whole-of-git lock, off by default and separate from the work-destroying subset above, which is always on. Set git_readonly = true at the top level of rules.toml to turn it on. When on:
- Blocks
git add,git commit,git tag - Blocks
git push(including force),git pull,git merge,git rebase,git reset,git clean - Blocks
git checkout,git restore,git revert,git cherry-pick,git stash,git bisect,git am,git apply - Blocks
git branch -d/-D - Allows read-only commands:
log,status,diff,show,branch(list),blame
Advisory — non-blocking hints for better practices:
- Docker CLI usage when MCP tools are available
rgfor symbol lookups when aidex is availablergfor structural patterns when ast-grep is availablenpm install,cargo add,pip installwarnings about environment modificationgit clonewarnings about disk space and time
Shadow Mode
Rules can be set to shadow mode, where they log what they would have done without actually blocking. This is useful for testing new rules before deploying them:
# In ~/.warden/rules.toml
[safety]
patterns = [
{ match = "some-new-pattern", msg = "Testing this rule", shadow = true }
]
Shadow-mode entries show up in the dashboard and in warden --debug export data, so you can evaluate their accuracy before making them live.
Rule IDs
Every rule carries an ID, and there are two shapes of ID depending on where the rule comes from.
Named rules live in the restriction registry. Their IDs are slugs, and they are what the governance handlers — read, write, redirect, permission, substitution — report:
| ID | Category | Severity | Disableable |
|---|---|---|---|
safety.rm-rf | Safety | HardDeny | no |
safety.sudo | Safety | HardDeny | no |
substitution.grep | Substitution | HardDeny | yes |
substitution.cat | Substitution | HardDeny | yes |
read.dedup | Governance | Advisory | yes |
read.large-file | Governance | SoftDeny | yes |
write.sensitive-path | Governance | HardDeny | no |
redirect.grep-tool | Redirect | HardDeny | yes |
permission.credentials | Permission | HardDeny | no |
Substitution IDs are keyed by the command being replaced: substitution.grep, find, curl, cat, ts-node, ls, sd, du, tar, sort, rg.
Compiled pattern rules — the long regex lists for safety, destructive commands, hallucination, advisories, and sensitive paths — get positional IDs assigned at merge time, in list order: safety.0, destructive.3, hallucination.12, advisory.4, sensitive_deny.0, zero_trace.0, git_readonly.1. Patterns you add yourself are numbered separately by where they came from: <category>.global_0 for your ~/.warden/rules.toml, <category>.project_0 for a project’s .warden/rules.toml.
Positional IDs shift when the compiled list changes between releases, so treat them as debugging handles rather than stable configuration keys.
Rule IDs are used for:
- Disabling rules in
rules.toml:[restrictions] disable = ["substitution.cat", "read.post-edit"]. Anything in thesafety,destructive,hallucination,zero_trace,permission, orgit_readonlynamespaces is refused — that is the immutable floor. - Identifying a denial — the deny message ends with the ID in brackets.
- Rule-fire accounting — seeing how often a specific rule fires.
Viewing All Rules
warden --debug restrictions list
warden --debug restrictions list --category Substitution
This prints every registry rule with its ID, handler, category, severity (HardDeny, SoftDeny, or Advisory), whether it can be disabled, and a description.
warden --debug describe --all is the other view: a JSON dump of the compiled patterns and your overrides. Its rule entries carry pattern and message only — the substitutions block is the exception, carrying id, source, and target.