When you install agentjail, several core policy rulesets are embedded in the
binary via go:embed. You do not have to write a single rule to be protected
from the most common dangerous tool calls.
The embedded sources live in the repository under:
cmd/agentjail/policies/: core rules (file_policy.rego,command_policy.rego,mcp_policy.rego,web_policy.rego,internal_tools.rego)cmd/agentjail/library/: optional library rules that can be enabled/disabled
file_policy
rule_id prefix: file_policy/
Enforces two tiers of protection for file access (Read, Write, Edit, Bash):
Hard deny everywhere (regardless of working directory)
These paths and patterns are always denied, no matter where the agent is working:
Directories
| Path | Notes |
|---|---|
~/.ssh | SSH keys and known hosts |
~/.aws | AWS credentials and config |
~/.gnupg | GPG keyring |
~/Downloads | Browser download folder - see note below |
~/Desktop | Desktop folder |
~/.agentjail | agentjail config and rules (see file_policy/agentjail_self below) |
~/.config | XDG user config directory |
/etc and /private/etc | System configuration |
/var and /private/var (non-temp) | System variable data |
Credential-token stores (home-anchored)
| Path | Notes |
|---|---|
~/.npmrc | npm auth token |
~/.pypirc | PyPI upload credentials |
~/.git-credentials | Git credential store (tokens in URLs) |
~/.docker/config.json | Docker registry auth |
~/.kube/config | Kubernetes cluster credentials |
~/.cargo/credentials (and .toml) | crates.io token |
~/Library/Keychains | macOS keychain databases |
Sensitive basenames: ask inside project dir, deny outside
Files matching these patterns are asked when they are inside the granted project working directory, and denied everywhere else:
| Pattern | Notes |
|---|---|
.env and .env.* | Environment variable files |
.envrc | direnv config |
credentials or secrets (any file named exactly) | Generic credential files |
*.pem, *.key, *.p12, *.pfx, *.jks, *.keystore | Cryptographic key material |
.netrc | Machine credentials file |
id_rsa, id_ed25519, id_ecdsa, id_dsa | SSH private key files |
Project membership is boundary-safe (path == cwd OR startswith(path, cwd + "/")),
so a sibling directory like /proj2 never matches cwd=/proj.
~/Downloads: ask for media, deny for credentials
~/Downloads is no longer a hard deny. The file_policy/downloads_review rule
fires an ask for non-sensitive files (e.g. .srt, .pdf, .png, .mp4,
downloaded source archives). The agent must get explicit confirmation before
reading or writing anything in that directory.
Files in ~/Downloads that match the sensitive-basename patterns (.pem, .env,
id_rsa, etc.) still trigger the standard deny via
file_policy/sensitive_credential, exactly as they would anywhere else outside
the project directory.
Temp tree is allowed
The temp tree ($TMPDIR, /tmp, /var/folders/…) is allowed. Agents can
freely read and write temporary files.
file_policy/agentjail_self (locked)
Writes to ~/.agentjail/ get their own hard-deny rule (file_policy/agentjail_self).
This rule is part of the locked self-protection set and cannot be disabled.
command_policy
rule_id prefix: command_policy/
Enforces safe command patterns regardless of file paths. Verdicts are deny or ask (confirm before proceeding).
Denied (blocked outright)
| Pattern | Reason |
|---|---|
curl or wget piped into bash or sh | Remote code execution via download |
Any command containing sudo | Privilege escalation |
dd if=/dev/* | Raw device write |
chmod 777 | World-writable permission grant |
Redirect to block device (> /dev/disk*, /dev/sd*, /dev/nvme*, /dev/mmcblk*) | Overwrite block device |
rm -rf on absolute paths (except the temp tree) | Destructive recursive delete |
git push --force to main/master | Destructive history rewrite on the default branch |
env or printenv piped to curl | Environment variable exfiltration |
gpg --export-secret-keys | Secret key export |
launchctl bootout or launchctl remove | macOS service removal |
systemctl stop or systemctl disable | Linux service shutdown |
ssh-keygen -f targeting a path outside /tmp | Key generation outside temp dir |
Any Bash command referencing a sensitive path listed in file_policy | Cross-policy enforcement |
Requires confirmation (ask)
| Pattern | Reason |
|---|---|
git push (non-force) | Remote repository write |
git push --force with no explicit branch (git push -f) | Target is the implicit current branch; confirm it is not the default branch |
npm/yarn/pnpm publish, cargo publish, pip upload, twine upload, gem push, poetry publish, docker push, gh release create | Package / image / release publish |
curl -O downloading to a path other than /tmp | Persistent file download |
Force-push branch awareness
command_policy distinguishes three cases for git push --force (or -f /
--force-with-lease / +<refspec>):
- Deny - the command names
mainormaster(e.g.git push origin main --force,git push --force-with-lease origin +main). Rewrites shared history. - Allow - the command names an explicit non-default branch (e.g.
git push origin my-feature -f). Normal rebase / PR-update workflow. - Ask - the command contains a force flag but no explicit branch
(
git push -f). The daemon cannot read the current branch from the command string, so it asks rather than guessing.
command_policy/no-policy-mutation (locked)
An always-on, locked rule blocks an agent from running agentjail policy disable, agentjail mcp, or writing into ~/.agentjail/. This is part of
the locked self-protection set and cannot be disabled.
Always-on self-protection rules (locked)
In addition to the core rulesets above, two rules guard agentjail itself and are part of the locked set, they can never be disabled:
| Rule ID | What it blocks |
|---|---|
library/no-daemon-kill | kill/pkill/killall targeting agentjail-daemon |
library/no-hook-self-disable | Writes to ~/.claude/, ~/.codex/, ~/.cursor/ settings (an agent removing its own hook) |
These rules are always-on core rules, not opt-in. They appear as locked in
agentjail policy list.
mcp_policy
rule_id prefix: mcp_policy/
Controls which MCP servers the agent is permitted to call.
Trust-on-install: when you run agentjail install, the installer
automatically seeds mcp.allowed in policy.yaml from the MCP servers already
configured in your agents (Claude/Codex/Cursor), so existing servers like
claude-mem, context7, or github keep working immediately.
Default blocklist: *stripe*, *payment*, *billing*, *twilio*,
*sendgrid* are always blocked. The blocklist always takes precedence over the
allowlist.
Server-level controls
Manage with agentjail mcp:
agentjail mcp list # current allowed + blocked
agentjail mcp allow claude-mem # trust a server
agentjail mcp block my-payment-bot # block a server
Or edit ~/.agentjail/policy.yaml directly:
mcp:
allowed: ["claude-mem", "context7", "github*"]
blocked: ["*stripe*", "*payment*"]
Per-tool granular controls (v0.2.8+)
Beyond server-level allow/block, you can gate individual tools within an
allowed MCP server using blocked_tools and ask_tools:
mcp:
allowed: ["github"]
blocked_tools: ["github/delete_repo", "github/transfer_repo"]
ask_tools: ["github/create_repo"]
Manage via CLI:
agentjail mcp tool allow github/list_repos
agentjail mcp tool block github/delete_repo
agentjail mcp tool ask github/create_repo
MCP inventory (v0.2.9+)
Get a full picture of every MCP server across your team:
agentjail mcp inventory
Scans agent configs (Claude, Codex, Cursor), npm packages, pip packages, and Docker containers for MCP servers. Each entry includes provenance metadata and a security audit flag.
Per-project policy (v0.2.9+)
Policies cascade from global defaults to per-project overrides. Use the
web UI project selector or configure in policy.yaml:
projects:
/path/to/project:
mcp:
allowed: ["project-specific-server"]
blocked: ["*billing*"]
Per-skill controls (v0.2.9+)
Gate agent skills with the same allow/block/ask model:
agentjail skill allow code-review
agentjail skill block deploy-to-prod
agentjail skill ask run-migration
web_policy
rule_id prefix: web_policy/
Governs the agent’s read-only web tools (WebSearch and WebFetch). Without
this policy every web call would fall through to resolver/default → ask,
creating constant noise for what is, in the common case, a benign read.
| Tool | Default verdict | Rule ID |
|---|---|---|
WebSearch | allow (always) | web_policy/search |
WebFetch | allow (read-only GET) | web_policy/fetch |
WebFetch to a blocked host | deny | web_policy/fetch_blocked |
WebSearch is a query to the agent harness’s own search backend, not an
arbitrary endpoint, so it is always allowed unconditionally.
WebFetch is an HTTP GET and is allowed by default. You can block specific
hosts via glob patterns in policy.yaml:
web:
blocked: ["*.internal", "169.254.*", "*tracking*"]
The web.blocked globs match the hostname only (not the path). Use * to span
subdomains (*.corp.internal or *tracking*). Disabling web_policy/fetch
(via disabled_rules) reverts WebFetch to ask.
internal_tools
rule_id prefix: internal_tools/
Auto-allows agent harness tools that have no external side effects. Without this policy, every orchestration call (task management, plan mode, tool-schema lookup, etc.) would escalate to the user - pure noise.
Harness-internal tools (internal_tools/allow) - manage in-session state
only; never touch the filesystem, shell, network, or MCP servers:
| Category | Tools |
|---|---|
| Task/plan management | TaskCreate, TaskUpdate, TaskGet, TaskList, TaskOutput, TaskStop, TodoWrite, EnterPlanMode, ExitPlanMode, UpdatePlan, update_plan |
| Tool schema | ToolSearch |
| Worktree/orchestration | LSP, CronCreate, CronDelete, CronList |
| Messaging | SendMessage, ScheduleWakeup, DesignSync |
| Other | AskUserQuestion |
Benign tools (internal_tools/benign_allow) - touch the filesystem or shell
only in ways that are already governed or have no new side effect:
| Tool | Why it is safe |
|---|---|
Glob | Read-only path enumeration (returns paths, not file contents) |
BashOutput | Reads stdout/stderr of an already-approved background shell |
KillShell | Terminates an agent-spawned background shell by session ID |
Task / Agent | Dispatches a subagent - the subagent’s own tool calls fire this same hook and are independently governed |
Skill | Loads a skill’s instructions; anything it then executes (Bash, Read/Write) fires this hook too |
Workflow | Orchestrates multi-agent workflows; subagent calls are independently hooked |
EnterWorktree | Creates a git worktree for isolated work; tool calls within the worktree are independently hooked |
ExitWorktree | Exits/cleans up an agent-owned worktree |
Tools deliberately not covered here (they keep normal governance because they
have real, ungoverned side effects): Bash, Read, Write, Edit,
NotebookEdit, MCP resource tools, and all MCP server tools. Grep is also
excluded intentionally - it returns file contents and would bypass
file_policy’s sensitive-path deny.
Library rules (opt-in)
Shipped but not loaded by default: enable per your threat model with
agentjail policy enable <name>. They block behavior that is suspicious in most
contexts but legitimately used in some workflows, so they are opt-in to keep the
core rules false-positive-free.
| Rule ID | What it blocks |
|---|---|
library/no-shell-init-write | Writes to shell init files (~/.zshrc, ~/.bashrc, …): persistence |
library/no-app-binary-write | Writes into installed /Applications/*.app bundles |
library/no-launchctl | launchctl submit/load, osascript, at, crontab -e/-r: out-of-tree process spawn |
library/no-history-read | Reads of shell histories + browser cookies/history |
library/no-shell-eval | eval, bash -c $VAR, base64-decode pipelines: obfuscated execution |
library/no-destructive-git | Whole-tree git reset --hard, git clean -fdx, git restore . (single-file ops stay allowed) |
Rule IDs use the hyphenated form (e.g. library/no-shell-init-write) in
rule_id fields and disabled_rules config. When passing names to
agentjail policy enable/disable, both the hyphenated form and the underscore
stem are accepted (e.g. no_shell_init_write or no-shell-init-write).
Disabling rules and the locked set
Any rule that is not in the locked set can be suppressed:
agentjail policy list # on / off / locked for every rule
agentjail policy disable file_policy/sensitive_in_project # stop asking on in-project secrets
agentjail policy enable file_policy/sensitive_in_project # turn it back on
Disabling a core rule requires --force and an interactive TTY
confirmation: an agent cannot do this non-interactively. All mutations are
logged to ~/.agentjail/audit.log.
The following locked self-protection set can never be disabled by
policy.yaml edit or CLI, regardless of --force:
file_policy/agentjail_selflibrary/no-daemon-killlibrary/no-hook-self-disablecommand_policy/no-policy-mutationresolver/*
Verdicts
All rulesets produce one of three verdicts via the candidate → resolver → decision model:
| Verdict | Meaning |
|---|---|
allow | Tool call proceeds immediately. |
ask | The agent is prompted to confirm before continuing. |
deny | Tool call is blocked; the agent receives a rejection message. |
The fail-safe default (no candidates fire) is ask.
Reading the source
All shipped rules are plain Rego that you can audit before running. The canonical source is the agentjail repository on GitHub:
The core rules are in cmd/agentjail/policies/ and library rules are in
cmd/agentjail/library/.
Verifying what is active
Run agentjail policy list to see which rulesets are currently loaded:
agentjail policy list
RULE STATUS SOURCE
file_policy/sensitive_credential locked Core
command_policy/no-sudo on Core
library/no-shell-init-write off Optional
Core rules always show status on or locked. Library rules that you have
enabled appear with status on. See the
CLI reference for full policy list, policy enable/disable, policy add/remove, and mcp documentation.
Extending the defaults
The default bundle is a starting point. You can add your own Rego rules using
agentjail policy add (recommended: validates the authoring contract and
hot-reloads the daemon), or by dropping a .rego file into
~/.agentjail/rules/ and sending SIGHUP. See
Writing your first rule to get started.