A policy is a Rego file with one or more deny rules. Each rule inspects an
incoming tool call and fires when its conditions hold. If any rule fires, the
call is blocked and the message is returned to the agent.
This page walks through writing a single rule from scratch, loading it, and confirming it works.
Anatomy of a deny rule
deny[msg] {
input.tool_name == "Bash"
contains(input.tool_input.command, "/.ssh/")
msg := "Blocked: command targets sensitive path ~/.ssh/"
}
Breaking that down:
deny[msg]declares that this is a deny rule that produces a message.- The body is a conjunction: every line must hold for the rule to fire.
input.tool_nameis the name of the tool the agent is trying to call.input.tool_inputholds the tool’s arguments. For a Bash call,input.tool_input.commandis the shell command string.msgis the string returned to the agent when the rule fires.
If any condition does not hold, the rule does not fire. agentjail only blocks the call if at least one rule produces a message.
See The input schema for the full shape of
input and how fields vary by tool.
Write a rule
Place policy files in ~/.agentjail/rules/. The daemon loads every *.rego
file in that directory (non-recursive) on startup and on reload.
Create a file called ~/.agentjail/rules/my_policy.rego:
package my_policy
deny[msg] {
input.tool_name == "Bash"
contains(input.tool_input.command, ".env")
msg := "Blocked: command reads or modifies .env file"
}
The package name should match the file name by convention. The rule fires
whenever a Bash command string contains .env.
Load and list policies
After saving the file, confirm agentjail sees it:
agentjail policy list
You should see my_policy alongside the built-in rules.
No daemon restart is needed. Running agentjail policy enable <name> or
agentjail policy disable <name> sends SIGHUP and the daemon hot-reloads and
recompiles all rules atomically. If the daemon was not running when you saved
the file, it will pick it up on next start.
Test it
The fastest end-to-end test is to pipe a PreToolUse JSON payload directly to the hook (the daemon must be running):
Deny case: confirm the rule fires.
echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"cat .env"}}' \
| agentjail-hook
Expected output:
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Blocked: command reads or modifies .env file"}}
Exit code is 2 on deny.
Allow case: confirm a harmless call passes through.
echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"echo hello"}}' \
| agentjail-hook
Expected: exit code 0, permissionDecision is "allow".
For more on writing test cases, see Testing policies.
For the full set of fields available in input, see The input schema.
For the evaluation model (allow vs. deny semantics), see The policy model.