Rule Catalog & Categories
Belay decides what an AI coding agent may do using a deterministic rule catalog — a single, versioned source of truth that the daemon compiles in at build time and matches against every tool call. There is no LLM on the gating path: the same input always produces the same verdict, in well under 100ms.
When this matters
Every allow, ask, or deny you see in the audit log traces back to a rule in
this catalog (or to the session correlation layer
that sits on top of it). Understanding the categories helps you read verdicts,
tune false positives in --observe mode, and map findings to the security
standards your organisation reports against.
Mental model
A tool call (a Bash command, a file Read/Write/Edit, a Grep, a
WebFetch, or an MCP tools/call) is projected into a normalized shape and
tested against each rule. A rule declares which tools it applies_to and a set
of match patterns (command regexes or path-glob regexes). When a rule matches,
it contributes its decision — allow, ask, or deny — along with a
severity and its standards tags.
Categories
The catalog groups rules into behaviour categories. Each category below lists representative matches; it is not exhaustive.
| Category | What it catches | Typical verdict |
|---|---|---|
| Secrets | Reading .env, ~/.aws/credentials, SSH private keys, .netrc, .npmrc, kube/docker/gcloud config; dumping the process environment; grepping the filesystem for API_KEY/TOKEN/AKIA/sk-ant-; touching the OS/browser credential store | ask (often arms the session — see below) |
| Egress | Outbound to known exfiltration sinks (webhook.site, requestbin, ngrok, pastebin, transfer.sh, …); uploading .env or secret-shaped values over curl/scp/rsync; DNS-encoded exfiltration; reverse shells | ask → deny for the most dangerous forms |
| Destructive | rm -rf of a dangerous target, --no-preserve-root, disk/partition wipes (dd of=/dev/…, mkfs, wipefs), destructive SQL (DROP, TRUNCATE), git push --force to main / git reset --hard | deny (critical) or ask (git force) |
| RCE | curl … | sh and other download-and-pipe-to-interpreter patterns, decode-and-execute (base64 -d | sh, eval $(…)) | deny |
| Supply-chain | Package installs that may run install scripts (npm/pnpm/yarn install, pip install, gem/cargo/go install, npx -y) | ask |
| Persistence | Writing SSH authorized_keys, editing shell profiles (.bashrc/.zshrc), scheduled tasks (crontab, /etc/cron, systemctl enable, launchctl load) | deny (authorized_keys) or ask |
| Privilege-escalation | sudo / doas / pkexec | ask |
| Recon | Filesystem-wide secret sweeps (find … -name *.env), reading another agent's config/memory, cloud/identity probes (aws sts get-caller-identity, gh auth token, kubectl config view), local LLM-runtime probing | ask |
| Config-tamper | Modifying agent configuration (~/.claude/settings.json, apiKeyHelper, ANTHROPIC_BASE_URL, Codex config.toml) or Belay's own rules/audit/binary | deny (critical) |
Verdict precedence
When several rules match one tool call, Belay combines them with a strict priority:
Deny > Ask > Allow
If any matching rule says deny, the call is denied — a lower-priority ask
or allow can never override it. If no rule denies but at least one asks, the
call escalates to a human. Only when nothing denies or asks does the call
proceed.
The engine is fail-closed. An internal error, an evaluation timeout, or an
unanswered ask in the headless MCP proxy all resolve to deny — never to a
silent allow.
The dev-toolchain allowlist
Everyday build and version-control commands would otherwise trip ask rules
constantly (a pip install, a git add). To keep signal high, the catalog
ships a small dev-toolchain allowlist that suppresses benign activity:
cargo build|test|check|run|fmt|clippy|docnpm run|test|ci|build|start(and thepnpm/yarnequivalents)go build|test|vet|run|fmtdocker build|compose build|run --rmgit status|diff|log|add|commit|fetch|pull|branch|checkoutmake
The allowlist can only downgrade a non-deny decision. It can never rescue
a DENY. A destructive or exfiltration command that happens to start with an
allowlisted verb is still denied — the allowlist is a false-positive damper, not
an escape hatch.
Every verdict explains itself
A rule match is never just a bare deny. Each verdict carries a curated,
plain-English explanation — a short summary, what the command does, why it is
risky, when it is normal, and a suggested action — so you can decide without
reverse-engineering the rule. See Explain & Advise for
the built-in explanations and the optional (off-by-default) AI explainer.
Standards tags on every rule
Each rule carries three machine-readable tags so a finding is immediately mappable to the frameworks security teams already use:
- OWASP Agentic Security Initiative (ASI) Top 10 — e.g.
ASI02,ASI03,ASI05 - OWASP LLM Top 10 — e.g.
LLM01,LLM02 - MITRE ATLAS — e.g.
AML.Exfiltration,AML.CodeExecution,AML.Persistence
See Standards Alignment for the full mapping and how those tags flow into the scanner's SARIF 2.1.0 output.
Tuning false positives
Run belay protect <agent> --observe to log would-be decisions without
blocking anything, then review with belay logs. Once the audit trail
looks right for your workflow, re-run protect without --observe to enforce.