# agentjail > Policy guardrails for coding agents. Block dangerous tool calls before your agent fires them. Open source, Apache-2.0. # Introduction Source: https://agentjail.io/docs/introduction.md Description: What agentjail is, the problem it solves, and how policy enforcement sits between your coding agent and the tools it calls. agentjail is a policy guardrail for coding agents. It sits at the boundary between your agent and the tools it can call (the shell, the filesystem, the network) and blocks dangerous tool calls **before** they execute. ## Why it exists Coding agents are useful precisely because they take actions on your behalf. That same capability is the risk: a well-meaning but vague instruction can lead an agent to delete the wrong directory, leak a secret, or push to the wrong remote. Most safety today is either a coarse sandbox (all-or-nothing, awkward to live in) or a permission prompt on every action (fine until there are dozens per task and fatigue sets in). agentjail takes a third path: it doesn't try to make the agent smarter, and it doesn't ask you to approve everything. It enforces **policy at the boundary**, regardless of the agent's intent. ## How it works Every time your agent is about to use a tool, agentjail intercepts the call, evaluates it against your policy, and returns a verdict. Allowed calls run as normal; denied calls never reach the shell. The agent gets a structured block response and explains itself instead of proceeding. ```text your agent ──tool call──▶ agentjail ──allow──▶ shell / files / network (Claude Code) (policy gate, └─deny──▶ blocked offline) (e.g. rm -rf ~/.ssh) ``` Three properties make this practical: - **It runs at the tool boundary:** on the `PreToolUse` hook, before the command is ever handed to the shell. - **It's offline:** rules are evaluated locally, with no network round-trip and no model in the decision loop. Fast and deterministic. - **It's auditable:** every rule is plain text you can read, diff, and version alongside your project. ## What it can guard A policy can inspect any tool call your agent makes, so you can write rules over: - **Shell commands:** block destructive or sensitive operations. - **Filesystem paths:** keep the agent out of `~/.ssh`, `.env`, credentials, or anything outside the working tree. - **Network access:** stop exfiltration to unexpected hosts. - **Git actions:** prevent pushes to protected remotes or force-pushes. ## What a rule looks like Policies are written in [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/). A rule is just a condition over the structured tool call: when it matches, the call is denied with a message: ```rego deny[msg] { input.tool_name == "Bash" contains(input.tool_input.command, "/.ssh/") msg := "Blocked: command targets sensitive path ~/.ssh/" } ``` One rule. Offline. No round-trips. The [policy model](/docs/concepts/policy-model) page covers how rules are matched and evaluated. ## Where it fits agentjail is most useful anywhere an agent can touch something you care about: - **Local development:** a safety net while you let an agent work in your repo. - **"Skip the prompts" workflows:** when you run an agent with permission prompts disabled, agentjail is the boundary that still holds. agentjail currently supports **Claude Code on macOS**. Codex, Cursor, CI, and other integrations are not yet available. ## Next steps - **[Quickstart](/docs/quickstart):** zero to a blocked tool call in about two minutes. - **[Installation](/docs/installation):** get agentjail running locally. - **[The policy model](/docs/concepts/policy-model):** how rules are written and evaluated. # Quickstart Source: https://agentjail.io/docs/quickstart.md Description: Go from zero to a blocked tool call in about two minutes. This page gets you from zero to a live, blocked tool call in about two minutes. For a more detailed setup walkthrough, see [Installation](/docs/installation). > **Prerequisites:** macOS (arm64 or amd64). Claude Code is the only supported > agent today; Codex and Cursor support is not yet available. ## 1. Install agentjail ```sh curl -fsSL https://agentjail.io/install.sh | sh ``` This downloads the release tarball, verifies its SHA256 checksum, and installs the agentjail binaries to `~/.agentjail/bin/`. ## 2. Confirm the install ```sh agentjail --version ``` Then check that the default policies are loaded: ```sh agentjail policy list ``` You should see the core policies (such as `file_policy`) listed in the output. ## 3. Wire it into Claude Code ```sh agentjail install --for claude-code ``` This starts the background daemon (`agentjail-daemon`) and registers `agentjail-hook` as a `PreToolUse` hook in `~/.claude/settings.json`. From this point on, every tool call Claude Code is about to make is evaluated against your policy before it runs. Denied calls never reach the shell. ## 4. See a denial in action With the daemon running, pipe a `PreToolUse` payload directly to `agentjail-hook` to confirm the policy is working: ```sh echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"rm -rf ~/.ssh/"}}' \ | agentjail-hook ``` You should get a nonzero exit code and a message like: ```text DENY: Blocked: command targets sensitive path ~/.ssh/ ``` The call is blocked. Because `agentjail-hook` is wired into Claude Code's `PreToolUse` hook, the same evaluation happens automatically for every tool call Claude Code tries to make. ## Next steps - [How it works](/docs/concepts/how-it-works): understand the tool-call boundary and how evaluation runs offline. - [Installation](/docs/installation): the full setup guide, including binary paths and policy bundle details. # Installation Source: https://agentjail.io/docs/installation.md Description: Install agentjail and wire it into your coding agent in under a minute. agentjail installs a set of binaries plus a default policy bundle. The install script detects your platform and drops everything into place. > **Platform note:** the `agentjail install` command (daemon + hook wiring) is > macOS-only today. Claude Code is the only supported agent; Codex and Cursor > support is not yet available. ## Install ```sh curl -fsSL https://agentjail.io/install.sh | sh ``` This downloads the release tarball for your platform (macOS arm64/amd64, Linux arm64/amd64), verifies the SHA256 checksum, and installs the following binaries to `~/.agentjail/bin/`: - `agentjail` — the main CLI - `agentjail-hook` — the tiny binary called by Claude Code's `PreToolUse` hook - `agentjail-daemon` — the persistent background daemon that keeps OPA warm - `agentjail-shield` and `agentjail-netproxy` — supporting binaries Alternatively, if you use Homebrew: ```sh brew install agentjail/tap/agentjail ``` ## Verify ```sh agentjail --version agentjail policy list ``` The first command confirms the binary is on your `PATH`; the second prints the policies that are currently active. ## Wire it into Claude Code Run the install command to register the daemon and hook: ```sh agentjail install --for claude-code ``` This starts the `agentjail-daemon` (via launchctl on macOS) and writes the `PreToolUse` hook entry into `~/.claude/settings.json`: ```json { "hooks": { "PreToolUse": [ { "matcher": "*", "hooks": [{ "type": "command", "command": "~/.agentjail/bin/agentjail-hook" }] } ] } } ``` From this point on, every tool call Claude Code is about to make passes through `agentjail-hook`, which forwards it to the daemon for policy evaluation in under 5 ms. Allowed calls run as normal; denied calls never reach the shell. # How it works Source: https://agentjail.io/docs/concepts/how-it-works.md Description: How agentjail intercepts tool calls, evaluates them offline, and returns a verdict before any command reaches the shell. agentjail sits at the boundary between your coding agent and the tools it can call. It intercepts every outgoing tool call, evaluates it against your policy, and returns a verdict before the call is handed off to the shell, filesystem, or network. ## The tool-call boundary Coding agents expose a lifecycle hook called `PreToolUse`. This hook fires before a tool call executes, giving agentjail a chance to inspect it and either pass it through, block it, or ask the user to confirm. ```text your agent ──tool call──▶ agentjail-hook ──▶ agentjail-daemon ──allow──▶ shell / files / network (Claude Code) (PreToolUse hook) (OPA / Rego) └─ask───▶ user prompted └─deny──▶ blocked (e.g. rm -rf ~/.ssh) ``` The `agentjail-hook` binary (installed in `~/.agentjail/bin/`) receives the tool-call payload and forwards it over a Unix socket to the persistent `agentjail-daemon` process. The daemon keeps OPA warm and evaluates your Rego policy in under 5 ms, then returns the verdict. The hook prints the decision to the agent and exits. Nothing runs until agentjail has evaluated the call. If the call is denied, the command never reaches the shell. > **Integration status:** Claude Code is fully supported. Codex and Cursor > integrations are not yet available. ## What agentjail evaluates Each tool call arrives as a structured object. The fields your policy can inspect include: - `input.tool_name`: the name of the tool being called (for example, `"Bash"`). - `input.tool_input`: the arguments for that tool (for example, `{"command": "rm -rf ~/.ssh/"}`). - `input.hook_event`: the lifecycle hook name (always `"PreToolUse"`). - `input.session_id`: the agent session identifier. - `input.cwd`: the agent's current working directory. For a `Bash` tool call the shell command is at `input.tool_input.command`. For `Write` and `Edit` calls the target file is at `input.tool_input.file_path`. MCP tools surface as `input.tool_name` values like `mcp__server__tool`. Your policy rules inspect these fields and decide whether to allow, ask, or deny. See [The policy model](/docs/concepts/policy-model) for how rules are written. ## Offline evaluation Evaluation runs entirely on the local machine. There is no network call at decision time, no external service, and no model in the decision loop. This keeps verdicts fast and deterministic: the same tool call produces the same verdict every time, regardless of network conditions or service availability. ## Allow, ask, and deny There are three possible verdicts: - **allow** — the call proceeds normally. - **ask** — the hook prompts the user to confirm before proceeding. - **deny** — the call is blocked; the command never reaches the shell. A call is **denied** if any `deny` rule in your policy fires. If an `ask` rule fires (and no `deny` rule does), the user is prompted. If neither fires, the call is **allowed**. See [Verdicts](/docs/concepts/verdicts) for the full semantics and what the agent receives on each outcome. ## What happens on a denial When a call is denied, agentjail returns a structured block message to the agent and exits with status code 2 (the Claude fast-block convention). The agent receives the denial reason and stops rather than proceeding. The command is never handed to the shell. You can test this manually while the daemon is running: ```sh echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"rm -rf ~/.ssh/"}}' \ | agentjail-hook ``` You can also unit-test Rego policies directly with `opa test`. ## Why this approach Enforcing policy at the tool boundary, offline, has a few practical consequences: - **No false sense of safety from latency.** There is no window between "agent decides to act" and "policy is checked." The check is synchronous and happens before execution. - **Auditable.** Every rule is plain text you can read, diff, and version alongside your project. - **Works without network.** Useful in air-gapped environments, strict CI, or anywhere an outbound call would be blocked. ## Next steps - [The policy model](/docs/concepts/policy-model): how rules are written and evaluated. - [Verdicts](/docs/concepts/verdicts): the exact semantics of allow, ask, and deny. # The policy model Source: https://agentjail.io/docs/concepts/policy-model.md Description: How agentjail policies are written, matched against tool calls, and evaluated offline with deny-by-rule semantics. agentjail policies are written in [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/), the same language used by Open Policy Agent. Each policy is a set of rules that inspect an incoming tool call and decide whether to allow or deny it. ## The shape of a tool call Every evaluation receives a structured `input` describing the call the agent wants to make: ```json { "tool": "Bash", "tool_input": { "command": "rm -rf ~/.ssh/" } } ``` ## A rule that fires A `deny` rule matches when its body holds. When any `deny` produces a message, the call is blocked and that message is returned to the agent. ```rego deny[msg] { input.tool == "Bash" path := input.tool_input.command contains(path, "/.ssh/") msg := "Blocked: command targets sensitive path ~/.ssh/" } ``` One rule. Offline. No round-trips. ## Evaluation semantics - Rules are evaluated locally: there is no network call at decision time. - A call is denied if **any** `deny` rule produces a message; otherwise it is allowed. - Policies are plain text you can read, diff, and version-control alongside the rest of your project. See **[Installation](/docs/installation)** to get a working policy bundle on your machine. # Verdicts Source: https://agentjail.io/docs/concepts/verdicts.md Description: The three verdicts agentjail can return, what triggers each one, and what the agent receives for each outcome. Every tool call evaluated by agentjail produces exactly one of three verdicts: **allow**, **ask**, or **deny**. ## Allow A call is allowed when no `deny` or `ask` rule in the active policy fires. Allowed calls pass through to the shell, filesystem, or network exactly as the agent intended. agentjail exits with status code 0 and does nothing further. ## Ask A call produces an **ask** verdict when an `ask` rule fires and no `deny` rule fires. The agent is prompted to confirm before the call proceeds. agentjail exits with status code 0 for ask verdicts (the agent handles the confirmation UI). ## Deny A call is denied when at least one `deny` rule fires and produces a message. It only takes one rule to block a call: if multiple rules match, the call is still denied (with the message from whichever rule fired, or all of them, depending on how the policy is structured). When a call is denied: - agentjail exits with **status code 2** (the Claude fast-block convention). - A **structured block message** is returned to the agent describing why the call was blocked. - The command **never reaches the shell**. No side effects occur. ### What the agent sees The hook returns a Claude-format JSON verdict to the agent: ```json { "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Blocked: command targets sensitive path ~/.ssh/" } } ``` The `permissionDecision` field is `"allow"`, `"ask"`, or `"deny"`. The `permissionDecisionReason` comes from the `msg` binding in the matching Rego rule. For example, if this rule fires: ```rego deny[msg] { input.tool_name == "Bash" contains(input.tool_input.command, "/.ssh/") msg := "Blocked: command targets sensitive path ~/.ssh/" } ``` The agent receives the JSON above with `permissionDecision: "deny"` and stops rather than proceeding. In practice, most agents will report the denial to the user and wait for further instruction. ### Testing a verdict You can evaluate any tool call against your active policy directly from the command line while the daemon is running: ```sh echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"rm -rf ~/.ssh/"}}' \ | agentjail-hook ``` A denied call exits with code 2 and prints the denial reason. An allowed call exits with code 0. You can also unit-test your Rego policy files directly with `opa test` without needing the daemon running. ### Fail-open on internal error If the hook encounters an internal error (for example, cannot reach the daemon), it fails open: it allows the call and exits with code 0. This ensures agent work is not silently interrupted by infrastructure issues, but you should monitor `agentjail status` and `agentjail logs` to catch daemon problems early. ## The deny-by-rule model The semantics are worth stating plainly: **allow is the default**. If no rule matches, the call goes through. You write rules to deny (or ask about) specific things rather than rules to allow a specific set of things. This keeps policies focused and easy to read: each rule expresses exactly one thing you want to block or confirm. See [The policy model](/docs/concepts/policy-model) for how `deny` and `ask` rules are written and what fields are available in `input`. # Your first rule Source: https://agentjail.io/docs/policies/first-rule.md Description: Anatomy of a deny rule, how to write one that blocks a command, where to put it, and how to test it. 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 ```rego 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_name` is the name of the tool the agent is trying to call. - `input.tool_input` holds the tool's arguments. For a Bash call, `input.tool_input.command` is the shell command string. - `msg` is 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](/docs/policies/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`: ```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: ```sh agentjail policy list ``` You should see `my_policy` alongside the built-in rules. No daemon restart is needed. Running `agentjail policy enable ` or `agentjail policy disable ` 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. ```sh echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"cat .env"}}' \ | agentjail-hook ``` Expected output: ```json {"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. ```sh 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](/docs/policies/testing). For the full set of fields available in `input`, see [The input schema](/docs/policies/input-schema). For the evaluation model (allow vs. deny semantics), see [The policy model](/docs/concepts/policy-model). # The input schema Source: https://agentjail.io/docs/policies/input-schema.md Description: The shape of the input object passed to every policy rule, with a JSON example and notes on per-tool fields. Every rule receives the same `input` object describing the tool call the agent is about to make. Understanding its shape is the starting point for writing any rule. ## Top-level fields ```json { "hook_event": "PreToolUse", "tool_name": "Bash", "tool_input": { "command": "rm -rf ~/.ssh/" }, "session_id": "abc123", "cwd": "/home/user/project" } ``` Fields always present: - `input.hook_event`: the lifecycle event name (e.g. `"PreToolUse"`). - `input.tool_name`: the name of the tool as a string (e.g. `"Bash"`). - `input.tool_input`: an object whose keys depend on the tool being called. - `input.session_id`: the current agent session identifier. - `input.cwd`: the working directory the agent is operating in. ## Bash For the `Bash` tool, `tool_input` contains: | Field | Type | Description | |---|---|---| | `command` | string | The shell command string the agent wants to run. | Example: ```json { "hook_event": "PreToolUse", "tool_name": "Bash", "tool_input": { "command": "cat ~/.ssh/id_rsa" } } ``` Rule fragment: ```rego input.tool_name == "Bash" input.tool_input.command # the command string ``` ## File tools | Tool | Relevant `tool_input` field | |---|---| | `Write` | `file_path` | | `Edit` | `file_path` | | `Read` | `file_path` (also accepted as `path`) | | `Rename` | `old_path` | | `NotebookEdit` | `notebook_path` | Example rule blocking writes to sensitive paths: ```rego deny[msg] { input.tool_name == "Write" contains(input.tool_input.file_path, "/.ssh/") msg := "Blocked: write to ~/.ssh/ is not allowed" } ``` ## MCP tools MCP tool names follow the pattern `mcp____`. Match on `input.tool_name` using `startswith` or `regex.match`: ```rego deny[msg] { startswith(input.tool_name, "mcp__") input.tool_input.url # field name varies per MCP server not startswith(input.tool_input.url, "https://api.yourcompany.com") msg := "Blocked: MCP request to unexpected URL" } ``` The fields inside `tool_input` vary by MCP server. Common examples are `path` and `url`, but check your server's schema for the authoritative list. ## Network and git operations There is no dedicated network tool or git tool. Commands such as `curl`, `wget`, and `git` are invoked through the `Bash` tool and appear as plain strings in `input.tool_input.command`. Match them with string functions: ```rego # Block curl to unknown hosts deny[msg] { input.tool_name == "Bash" contains(input.tool_input.command, "curl") not contains(input.tool_input.command, "api.yourcompany.com") msg := "Blocked: curl to an unexpected host" } # Block git push with --force deny[msg] { input.tool_name == "Bash" contains(input.tool_input.command, "git push") contains(input.tool_input.command, "--force") msg := "Blocked: force push is not allowed" } ``` ## Inspecting a real call To see the exact JSON a rule will receive, pipe a PreToolUse payload to the hook while the daemon is running: ```sh echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"ls -la"}}' \ | agentjail-hook ``` The hook prints the full `hookSpecificOutput` response, including the `permissionDecision` and `permissionDecisionReason`. There is no separate verbose or debug flag that dumps the raw input document. ## Writing rules that are safe to fail When a field you reference does not exist in `tool_input`, the Rego expression is undefined and the rule does not fire. That means a rule like this: ```rego deny[msg] { input.tool_name == "Bash" contains(input.tool_input.command, "secret") msg := "Blocked: possible secret in command" } ``` will simply not fire for any non-Bash tool, because `input.tool_name == "Bash"` will be false. You do not need a separate guard for every tool. For practical rule examples built on this schema, see [Rule recipes](/docs/policies/recipes). # Rule recipes Source: https://agentjail.io/docs/policies/recipes.md Description: Copy-pasteable deny rules for common cases: sensitive paths, destructive commands, network egress, and git push guards. These rules are ready to drop into a policy file. Each covers a common scenario. Adjust the strings and messages to fit your setup. For the fields each rule references, see [The input schema](/docs/policies/input-schema). ## Block access to sensitive paths Deny any Bash command that touches `~/.ssh`: ```rego deny[msg] { input.tool_name == "Bash" contains(input.tool_input.command, "/.ssh/") msg := "Blocked: command targets ~/.ssh/" } ``` Deny access to `.env` files: ```rego deny[msg] { input.tool_name == "Bash" contains(input.tool_input.command, ".env") msg := "Blocked: command references a .env file" } ``` Deny access to `~/.aws` credentials: ```rego deny[msg] { input.tool_name == "Bash" contains(input.tool_input.command, "/.aws/") msg := "Blocked: command targets ~/.aws/ credentials" } ``` You can also block file-tool access to sensitive paths: ```rego deny[msg] { input.tool_name == "Read" contains(input.tool_input.file_path, "/.ssh/") msg := "Blocked: read from ~/.ssh/ is not allowed" } deny[msg] { input.tool_name == "Write" endswith(input.tool_input.file_path, ".pem") msg := "Blocked: write to a .pem file is not allowed" } ``` ## Block destructive commands Deny `rm -rf` invocations: ```rego deny[msg] { input.tool_name == "Bash" contains(input.tool_input.command, "rm -rf") msg := "Blocked: rm -rf is not allowed" } ``` Deny pipe-to-shell patterns (a common exfiltration or supply-chain vector): ```rego deny[msg] { input.tool_name == "Bash" contains(input.tool_input.command, "| sh") msg := "Blocked: pipe-to-shell pattern detected" } deny[msg] { input.tool_name == "Bash" contains(input.tool_input.command, "| bash") msg := "Blocked: pipe-to-bash pattern detected" } ``` ## Block unexpected network egress There is no dedicated network tool. `curl`, `wget`, and similar commands run through the `Bash` tool, so match on the command string: ```rego deny[msg] { input.tool_name == "Bash" contains(input.tool_input.command, "curl") not contains(input.tool_input.command, "api.yourcompany.com") msg := "Blocked: curl to an unexpected host" } ``` Apply the same pattern for `wget`: ```rego deny[msg] { input.tool_name == "Bash" contains(input.tool_input.command, "wget") not contains(input.tool_input.command, "api.yourcompany.com") msg := "Blocked: wget to an unexpected host" } ``` ## Block git push to protected remotes There is no dedicated git tool. Git commands run through the `Bash` tool, so match on the command string. Deny any `git push` that targets `origin` (adjust to your protected remote name): ```rego deny[msg] { input.tool_name == "Bash" contains(input.tool_input.command, "git push") contains(input.tool_input.command, "origin") msg := "Blocked: git push to origin requires manual approval" } ``` Deny force-push specifically: ```rego deny[msg] { input.tool_name == "Bash" contains(input.tool_input.command, "git push") contains(input.tool_input.command, "--force") msg := "Blocked: force push is not allowed" } deny[msg] { input.tool_name == "Bash" contains(input.tool_input.command, "git push") contains(input.tool_input.command, "-f ") msg := "Blocked: force push is not allowed" } ``` ## Combining conditions Rules are conjunctions: all lines in the body must hold. You can combine conditions freely: ```rego deny[msg] { input.tool_name == "Bash" contains(input.tool_input.command, "curl") contains(input.tool_input.command, "/.ssh/") msg := "Blocked: curl with SSH path looks like exfiltration" } ``` This fires only when both `curl` and `/.ssh/` appear in the same command. For testing that these rules fire correctly, see [Testing policies](/docs/policies/testing). # Testing policies Source: https://agentjail.io/docs/policies/testing.md Description: Iterate on rules using opa test for unit tests and the agentjail-hook pipe for end-to-end verification. There are two complementary ways to test agentjail policies: **Rego unit tests** with `opa test`, and **end-to-end hook tests** by piping a PreToolUse payload to `agentjail-hook`. There is no dedicated `agentjail policy test` command. For the full CLI reference, see [CLI reference](/docs/reference/cli). ## End-to-end hook testing (daemon required) The most direct test is to pipe the exact JSON the Claude hook sends, and observe the verdict. The daemon must be running. **Deny case:** confirm the rule fires. ```sh echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"cat ~/.ssh/id_rsa"}}' \ | agentjail-hook ``` Expected output (exit code `2`): ```json {"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Blocked: command targets sensitive path ~/.ssh/"}} ``` **Allow case:** confirm a harmless call passes through. ```sh echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"cat README.md"}}' \ | agentjail-hook ``` Expected: exit code `0`, `permissionDecision` is `"allow"`. **Ask/confirm case:** some default rules return `"ask"` instead of outright denying. Exit code is `0` for `ask`, the same as `allow`; inspect `permissionDecision` in the output to distinguish them. If the allow case is also denied, a different rule is firing. Check `agentjail policy list` to see which policies are active and narrow down which rule matches. ## Rego unit tests with `opa test` For offline, fast, repeatable tests, write `*_test.rego` files alongside your policy files. The daemon skips `*_test.rego` at runtime; they are for `opa test` only. ```rego # my_policy_test.rego package my_policy_test import data.my_policy test_ssh_command_is_denied { deny["Blocked: command targets ~/.ssh/"] with input as { "hook_event": "PreToolUse", "tool_name": "Bash", "tool_input": {"command": "cat ~/.ssh/id_rsa"} } } test_safe_command_is_allowed { count(deny) == 0 with input as { "hook_event": "PreToolUse", "tool_name": "Bash", "tool_input": {"command": "cat README.md"} } } ``` Run all tests in the rules directory: ```sh opa test ~/.agentjail/rules/ ``` `opa test` exits nonzero if any test fails, making it safe to run in CI. ## The basic workflow Write a rule, then immediately cover the two cases that matter: one input that should be denied, and one that should pass through. 1. Write the rule in `~/.agentjail/rules/my_policy.rego`. 2. Run `agentjail policy list` to confirm the daemon sees the policy. 3. Run the deny case via `agentjail-hook` pipe (or a `_test.rego` unit test). 4. Run one or two allow cases with inputs that should not match. 5. If both behave as expected, the rule is ready. ## Iterating on a rule If a rule is not firing when you expect it to, check these common issues: - **Wrong tool name:** `input.tool_name` is case-sensitive. Use the exact string your agent sends (e.g. `"Bash"`, not `"bash"`). - **Wrong field name:** `input.tool_input.command` only exists for Bash calls. For other tools, the field names differ. See [The input schema](/docs/policies/input-schema). - **Condition not holding:** Rego rules are conjunctions. If any line is false or undefined, the rule does not fire. Simplify the rule to a single condition and add conditions back one at a time. ## Testing across multiple tools Pass the `tool_input` shape that matches the tool you want to test: ```sh # Bash echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"ls"}}' \ | agentjail-hook # Write echo '{"hook_event_name":"PreToolUse","tool_name":"Write","tool_input":{"file_path":"/etc/passwd"}}' \ | agentjail-hook # Read echo '{"hook_event_name":"PreToolUse","tool_name":"Read","tool_input":{"file_path":"~/.aws/credentials"}}' \ | agentjail-hook ``` See [The input schema](/docs/policies/input-schema) for the correct `tool_input` fields for each tool. ## Checking which rule fired If a call is denied and you have multiple policies loaded, the denial message tells you which rule matched. Make your `msg` strings descriptive enough to distinguish rules at a glance: ```rego msg := "Blocked [my_policy/ssh-guard]: command targets ~/.ssh/" ``` That way, the `permissionDecisionReason` in the hook output tells you exactly which rule and policy fired. For a broader view, `agentjail logs -v` adds a summary line per event that includes the command or file path, the reason, and the session ID. # Claude Code Source: https://agentjail.io/docs/integrations/claude-code.md Description: Wire agentjail into Claude Code's PreToolUse hook to enforce policy before every tool call. Claude Code supports a `PreToolUse` hook: a command that runs before every tool call and can block the call by exiting with a non-zero status. agentjail is designed to sit exactly there, and **Claude Code is the only agent with a turnkey installer today** (macOS only). ## How it works When agentjail is installed for Claude Code, it registers `agentjail-hook` as the `PreToolUse` hook. Before each tool invocation Claude Code writes a JSON event to the hook's stdin; `agentjail-hook` evaluates it against your local Rego policy and responds with a decision. A denial exits 2, which causes Claude Code to stop the call and surface the block reason instead of running the command. For a full explanation of the hook protocol and how to test your policy interactively, see the [generic hook guide](/docs/integrations/generic-hook). ## Install **Quick install (recommended):** ```sh curl -fsSL https://agentjail.io/install.sh | sh ``` The script downloads the release tarball, verifies the SHA256 checksum, installs binaries to `~/.agentjail/bin/`, and runs `agentjail install --for claude-code`. **Homebrew:** ```sh brew install agentjail/tap/agentjail agentjail install --for claude-code ``` Both paths are macOS-only. Linux support is not yet available. ## What the installer writes `agentjail install --for claude-code` adds the following entry to `~/.claude/settings.json`: ```json { "hooks": { "PreToolUse": [ { "matcher": "*", "hooks": [ { "type": "command", "command": "~/.agentjail/bin/agentjail-hook" } ] } ] } } ``` Every tool call Claude Code makes will pass through `agentjail-hook` before execution. ## Verify it is working You can test `agentjail-hook` directly from the terminal (the daemon must be running). Pipe a synthetic `PreToolUse` event to the hook and inspect the output: ```sh echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' \ | agentjail-hook ``` The hook prints a JSON response: ```json { "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "..." } } ``` Exit 0 means allow or ask; exit 2 means deny. A denial from the test above confirms that agentjail is evaluating policy correctly. To confirm the live hook is gated, trigger a tool call inside Claude Code that your policy should block and verify the agent stops rather than proceeding. # Cursor Source: https://agentjail.io/docs/integrations/cursor.md Description: Wire agentjail into Cursor's pre-tool-use hook to enforce policy before every tool call. **Cursor integration is not yet available.** `agentjail install --for cursor` is a stub that currently exits with "not yet implemented". Claude Code is the only agent with a working turnkey installer today. Cursor support is planned for a future release. ## Planned design The intended integration follows the same pattern as the [generic hook guide](/docs/integrations/generic-hook): hook entries in `~/.cursor/hooks.json` for the `preToolUse` and `beforeShellExecution` events that pipe each tool-call event to `agentjail-hook` on stdin and read the decision from stdout. This design is **not wired yet**. Do not attempt to configure it manually — there is no supported path for Cursor at this time. ## What is available today - [Claude Code integration](/docs/integrations/claude-code): the only fully supported agent, with a turnkey installer on macOS. - [Generic hook guide](/docs/integrations/generic-hook): the hook protocol that any agent with a pre-tool-use callback can implement once support lands. Check the [changelog](/blog) or the [agentjail GitHub repository](https://github.com/agentjail/agentjail) for updates on Cursor support. # Codex Source: https://agentjail.io/docs/integrations/codex.md Description: Wire agentjail into Codex's pre-tool-use hook to enforce policy before every tool call. **Codex integration is not yet available.** `agentjail install --for codex` is a stub that currently exits with "not yet implemented". Claude Code is the only agent with a working turnkey installer today. Codex support is planned for a future release. ## Planned design The intended integration follows the same pattern as the [generic hook guide](/docs/integrations/generic-hook): a `PreToolUse` hook entry in `~/.codex/hooks.json` that pipes each tool-call event to `agentjail-hook` on stdin and reads the decision from stdout. This design is **not wired yet**. Do not attempt to configure it manually — there is no supported path for Codex at this time. ## What is available today - [Claude Code integration](/docs/integrations/claude-code): the only fully supported agent, with a turnkey installer on macOS. - [Generic hook guide](/docs/integrations/generic-hook): the hook protocol that any agent with a `PreToolUse` callback can implement once support lands. Check the [changelog](/blog) or the [agentjail GitHub repository](https://github.com/agentjail/agentjail) for updates on Codex support. # Generic pre-tool-use hook Source: https://agentjail.io/docs/integrations/generic-hook.md Description: How any coding agent with a pre-tool-use hook can call agentjail-hook to enforce policy before a tool call runs. agentjail is agent-agnostic at the protocol level. `agentjail-hook` reads a Claude Code-style `PreToolUse` JSON event from stdin and writes a decision to stdout. Any agent that can run a pre-tool-use command and feed that JSON shape can in principle use it — but **the only turnkey installer today is Claude Code** (macOS only). For other agents, this page describes the hook protocol so you can wire it manually once your agent supports it. ## The protocol `agentjail-hook` is a stdin/stdout process: - **Input (stdin):** a JSON object with at minimum: ```json { "hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": { "command": "rm -rf /" } } ``` `tool_name` is the tool schema name as your agent knows it (e.g. `Bash`, `Write`, `Read`). `tool_input` is the tool's input as a JSON object; for shell tools it is typically `{"command": "..."}`. - **Output (stdout):** a JSON object: ```json { "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "allow", "permissionDecisionReason": "..." } } ``` `permissionDecision` is `"allow"`, `"ask"`, or `"deny"`. - **Exit codes:** - **Exit 0** — allow or ask. The agent proceeds normally. - **Exit 2** — deny. The agent should stop the call and surface the reason. The evaluation is local and offline: no network round-trip, no model in the loop. The same input always produces the same verdict in milliseconds. ## Hook lifecycle The sequence at every tool call: ```text agent decides to call a tool | v pre-tool-use hook fires | v agentjail-hook reads JSON from stdin | exit 0? / \ yes no (exit 2) | | v v tool blocked: runs agent receives structured reason, explains itself ``` The agent never reaches the tool on a denial. The block happens before any command is handed to the shell, filesystem, or network. See [How it works](/docs/concepts/how-it-works) for the full lifecycle. ## Testing a rule before you wire it up You can test `agentjail-hook` directly from your terminal without a running agent. The daemon must be running (`agentjail status`). Pipe a synthetic event and inspect the output: ```sh # should deny (targets sensitive path) echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"cat ~/.ssh/id_rsa"}}' \ | agentjail-hook # should allow (safe read) echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"ls ./src"}}' \ | agentjail-hook ``` Check the exit code (`$?`) and the printed JSON to confirm your rules behave as expected before wiring them in. ## Next steps - [Claude Code integration](/docs/integrations/claude-code): the only turnkey integration today — wiring agentjail into Claude Code's PreToolUse hook. - [Cursor integration](/docs/integrations/cursor): planned, not yet available. - [Codex integration](/docs/integrations/codex): planned, not yet available. - [How it works](/docs/concepts/how-it-works): the full evaluation lifecycle. # CLI reference Source: https://agentjail.io/docs/reference/cli.md Description: The agentjail command-line interface: subcommands, flags, and example output. agentjail ships as two binaries: `agentjail` (the management CLI) and `agentjail-hook` (called automatically by the agent's `PreToolUse` hook). This page documents every subcommand of the management CLI, plus how to invoke the hook manually for testing. ## `agentjail version` Prints the installed version and exits. ```sh agentjail version ``` Example output: ```text agentjail v0.1.0-alpha ``` ## `agentjail install` Installs the hook and starts the daemon. The `--for` flag selects the target agent host. ```sh agentjail install --for ``` | Flag | Required | Description | |------|----------|-------------| | `--for` | yes | Agent host to install for. Only `claude-code` is implemented; `codex` and `cursor` exit with "not yet implemented". | agentjail install is macOS-only. ### Example ```sh agentjail install --for claude-code ``` ## `agentjail uninstall` Removes the hook and daemon installed by `agentjail install`. ```sh agentjail uninstall ``` ## `agentjail status` Prints whether the daemon is running and whether the hook is registered. ```sh agentjail status ``` ## `agentjail logs` Tails and filters the daemon audit log. ```sh agentjail logs agentjail logs -v ``` | Flag | Description | |------|-------------| | `-v` | Adds a secondary summary line per entry showing `command`/`file_path`, `reason`, and `session_id`. Does not dump the full input document. | ## `agentjail policy list` Prints a table of all policy rules with three columns: **RULE**, **STATUS**, and **SOURCE**. ```sh agentjail policy list ``` Example output: ```text RULE STATUS SOURCE file_policy core agentpolicy/policies/file_policy.rego command_policy core agentpolicy/policies/command_policy.rego mcp_policy core agentpolicy/policies/mcp_policy.rego network-guard enabled library secret-scanner disabled library ``` - **Core rules** (`file_policy`, `command_policy`, `mcp_policy`) always show status `core` and a source path under `agentpolicy/policies/`. - **Library rules** show `enabled` or `disabled` and source `library` (the literal string, not an on-disk path). ## `agentjail policy enable` / `agentjail policy disable` Toggle library rules on or off. The named rule is copied into (or removed from) `~/.agentjail/rules/` and the daemon receives a `SIGHUP` to hot-reload. ```sh agentjail policy enable agentjail policy disable ``` Core rules (`file_policy`, `command_policy`, `mcp_policy`) are always active and cannot be disabled; `disable` rejects them with an error. ## `agentjail ui` Opens the local web UI development tool. ```sh agentjail ui ``` ## Testing a tool call manually (`agentjail-hook`) Policy evaluation is performed by the `agentjail-hook` binary, not by a subcommand of the management CLI. The hook communicates over a Unix socket with the running daemon (which keeps OPA warm). Users do not run an eval command directly; to test a call manually, pipe a Claude `PreToolUse` JSON payload to the hook with the daemon already running: ```sh echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' \ | agentjail-hook ``` Output: ```json { "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Blocked: rm -rf on absolute path" } } ``` ### Exit codes | Code | Meaning | |------|---------| | `0` | Call is allowed or requires confirmation (`allow` / `ask`). | | `2` | Call is denied (`deny`). | ## See also - [Configuration](/docs/reference/configuration): config file, custom rules, and daemon flags. - [Default policies](/docs/reference/default-policies): what ships in the default bundle. # Default policies Source: https://agentjail.io/docs/reference/default-policies.md Description: The policy rulesets that ship with agentjail out of the box: file_policy, command_policy, and mcp_policy. When you install agentjail, three 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`) - `cmd/agentjail/library/` — optional library rules that can be enabled/disabled ## `file_policy` Blocks tool calls that read, write, or delete sensitive paths. The following paths and patterns are denied: **Directories** | Path | Notes | |------|-------| | `~/.ssh` | SSH keys and known hosts | | `~/.aws` | AWS credentials and config | | `~/.gnupg` | GPG keyring | | `~/Downloads` | Browser download folder | | `~/Desktop` | Desktop folder | | `~/.agentjail` | agentjail config and rules | | `~/.config` | XDG user config directory | | `/etc` and `/private/etc` | System configuration | | `/var` and `/private/var` | System variable data | **File name patterns** | 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 | Any Bash command whose input references one of the paths or patterns above is denied before it reaches the shell. ## `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 `/tmp/agentjail`) | Destructive recursive delete | | `git push --force` | Destructive history rewrite | | `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 | | `npm publish`, `cargo publish`, `pip upload`, `twine upload` | Package registry publish | | `curl -O` downloading to a path other than `/tmp` | Persistent file download | ## `mcp_policy` Controls which MCP servers the agent is permitted to call. By default the daemon uses an allowlist from `~/.agentjail/policy.yaml` under the `mcp.allowed` key. When the key is absent, all MCP calls are allowed. ## Verdicts All three rulesets produce one of three verdicts: | 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. | ## Reading the source All shipped rules are plain Rego that you can audit before running. The canonical source is the agentjail repository on GitHub: [github.com/LuD1161/agentjail](https://github.com/LuD1161/agentjail) 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: ```sh agentjail policy list ``` ```text RULE STATUS SOURCE file_policy core agentpolicy/policies/file_policy.rego command_policy core agentpolicy/policies/command_policy.rego mcp_policy core agentpolicy/policies/mcp_policy.rego ``` Core rules always show status `core`. Library rules that you have enabled appear with status `enabled` and source `library`. See the [CLI reference](/docs/reference/cli) for full `policy list` and `policy enable/disable` documentation. ## Extending the defaults The default bundle is a starting point. You can add your own Rego rules in `~/.agentjail/rules/` without modifying the shipped files. See [Writing your first rule](/docs/policies/first-rule) to get started. # Configuration Source: https://agentjail.io/docs/reference/configuration.md Description: What you can configure in agentjail: the policy.yaml overlay, custom Rego rules, daemon flags, and how rules compose. agentjail works with zero configuration: install it, and the three core rulesets (`file_policy`, `command_policy`, `mcp_policy`) are active immediately. When you want to tune behaviour you have three levers: the `policy.yaml` overlay file, user-supplied Rego rules, and daemon startup flags. ## Config file — `~/.agentjail/policy.yaml` The primary configuration file is `~/.agentjail/policy.yaml` (YAML). The daemon reads it at startup and re-reads it on `SIGHUP` (which `agentjail policy enable/disable` triggers automatically). The top-level keys map to per-category tuning: ```yaml file: extra_deny: - ~/Projects/secrets commands: extra_block: - "curl.*internal.corp" mcp: allowed: - filesystem - github network: allowed_hosts: - api.openai.com - api.anthropic.com ``` | Key | Purpose | |-----|---------| | `file.extra_deny` | Additional path patterns to block on top of the built-in file policy. | | `commands.extra_block` | Additional command patterns (regex) to deny on top of the built-in command policy. | | `mcp.allowed` | Allowlist of MCP server names the agent may call. | | `network.allowed_hosts` | Allowlist of hostnames the agent may reach. | A sample strict configuration suitable for tightly controlled environments is provided in the repository at `samples/configs/policy-strict.yaml`. ## Daemon startup flags | Flag | Description | |------|-------------| | `--policy ` | Path to a `policy.yaml` overlay (defaults to `~/.agentjail/policy.yaml`). | | `--rules ` | Directory of `*.rego` files to load in addition to the embedded core rules (non-recursive). | ## Adding custom Rego rules Drop a `.rego` file into `~/.agentjail/rules/`. The daemon loads every `*.rego` in that directory (non-recursive) at startup and on `SIGHUP`. Alternatively, point the daemon at a different directory with `--rules `. ```sh # Example: add a custom rule cp my-org-policy.rego ~/.agentjail/rules/my-org-policy.rego # Send SIGHUP to reload without restarting kill -HUP $(pgrep agentjail-daemon) ``` Any `deny` rule in any loaded file causes the call to be blocked. There is no concept of overriding a built-in rule — rules are additive. ## Which rules can be disabled **Core rules** (`file_policy`, `command_policy`, `mcp_policy`) are always active. `agentjail policy disable` rejects them with an error. These cannot be turned off. **Library rules** can be toggled individually: ```sh agentjail policy enable secret-scanner agentjail policy disable network-guard ``` Enabling a library rule copies it into `~/.agentjail/rules/` and sends `SIGHUP` to the daemon. Disabling removes it and sends `SIGHUP`. Per-category tuning (extra paths, extra blocked command patterns, allowlists) is done via `policy.yaml` keys, not by disabling core rules. ## Viewing active configuration `agentjail policy list` shows every rule, its status, and its source: ```sh agentjail policy list ``` ```text RULE STATUS SOURCE file_policy core agentpolicy/policies/file_policy.rego command_policy core agentpolicy/policies/command_policy.rego mcp_policy core agentpolicy/policies/mcp_policy.rego network-guard enabled library secret-scanner disabled library ``` Library rules show source `library` (a literal string, not an on-disk path). Core rules show their embedded source path under `agentpolicy/policies/`. ## See also - [Default policies](/docs/reference/default-policies): what the core rulesets block. - [CLI reference](/docs/reference/cli): `policy list`, `policy enable/disable`, and daemon flags. # Run in CI Source: https://agentjail.io/docs/guides/ci.md Description: How agentjail can protect agents running unattended in CI — and what is not yet available. In local development, you are usually nearby when an agent runs. In CI, you are not. An agent triggered by a pull request, a scheduled job, or an automated release pipeline can run for minutes before anyone notices something went wrong, and by then the damage is done. agentjail is designed to be the guard that stays in place when you are not watching — but **a wired CI integration path does not exist yet**. The `install` command is macOS-only and there is currently no mechanism for installing agentjail inside a Linux CI runner or wiring the hook from a CI job step. This page describes what is available today and the direction planned for the future. ## Why CI is different When an agent runs unattended: - There are no permission prompts to pause execution. The agent proceeds without checking. - Any mistake — a bad path, a leaked secret, a force-push to the wrong remote — executes immediately. - The blast radius can be larger than in a local session because CI agents often have broader credentials (deploy keys, cloud tokens, registry access). ## What is available today: the strict policy sample agentjail ships a sample config at `samples/configs/policy-strict.yaml` that is recommended for CI/CD or any max default-deny environment. If you are already running agentjail locally (macOS) and want to validate what a strict policy looks like before a CI path exists, that file is the reference starting point. Enable it with: ```sh agentjail policy list # see available rulesets agentjail policy enable # enable the strict ruleset ``` ## CI integration: not yet available A supported mechanism for running agentjail inside GitHub Actions, GitLab CI, CircleCI, or other CI environments does not exist yet. Specifically: - The install script and `agentjail install` command target macOS (launchctl-based daemon). - There is no `--ci` flag, Docker image, or GitHub Action published by agentjail. - `agentjail install --for codex` and `--for cursor` (which CI pipelines might use) are stubs that exit with "not yet implemented". When CI integration is available it will appear in the [changelog](/blog) and the [Claude Code integration guide](/docs/integrations/claude-code) will be updated accordingly. ## What happens on a denial (once wired) When agentjail blocks a tool call, `agentjail-hook` exits 2 and prints a structured reason. The agent receives the block message, stops, and typically logs an explanation. A CI job that fails loudly on a blocked call is better than one that silently succeeds on a destructive one. ## See also - [Claude Code integration](/docs/integrations/claude-code): the only fully supported agent integration today. - [Safely skipping permission prompts](/docs/guides/skip-permissions): the companion guide for running agents with prompts disabled. - [Generic hook guide](/docs/integrations/generic-hook): the hook protocol that a CI-aware integration would use. # Safely skipping permission prompts Source: https://agentjail.io/docs/guides/skip-permissions.md Description: How agentjail enforces a hard boundary when you run an agent with permission prompts disabled. Most coding agents ask for your approval before running a tool call. That is a reasonable default, but it breaks down at scale: once an agent is making dozens of calls per task, the prompts pile up, and approving them becomes mechanical. Fatigue sets in, and the safety guarantee collapses. The tempting solution is to disable prompts entirely and trust the agent to stay within reasonable bounds. The problem is that "trust the agent" is not a policy. It is an assumption. agentjail lets you skip the prompts without dropping the guardrail. ## How it works agentjail sits at the `PreToolUse` boundary. Before each tool call runs, `agentjail-hook` evaluates the call against your policy and either allows or denies it — regardless of whether the agent's own permission prompts are enabled or disabled. A call that matches a `deny` rule is blocked before it reaches the shell, the filesystem, or the network. The agent gets a structured block message and stops. Nothing executes. The result is that you can let an agent run at full speed without watching every call, and still have a deterministic, auditable boundary around what it is allowed to do. For a deeper look at how evaluation works, see [How it works](/docs/concepts/how-it-works). ## What to set up before you skip prompts agentjail must be in place before you reduce human oversight. Make sure: 1. agentjail is installed and `agentjail status` shows the daemon running. 2. `agentjail policy list` shows the rulesets you expect to be active. 3. You have tested your policy using `agentjail-hook` directly against the kinds of calls your agent will make (see the [generic hook guide](/docs/integrations/generic-hook) for the test pattern). Skipping prompts means you are relying on the policy to catch what you would have caught manually. Agent-specific instructions for disabling the agent's own permission prompts vary by tool and are outside the scope of this guide. Check your agent's documentation for its `--dangerously-skip-permissions` or equivalent flag. agentjail's hook remains active regardless of that setting. ## The key property: offline, deterministic enforcement When a human approves every tool call, safety depends on the human being present, attentive, and consistent. That is hard to scale. agentjail's policy evaluation is offline (no network round-trip, no model in the loop) and deterministic (the same input always produces the same verdict). The boundary holds whether the agent runs interactively, as a background job, or unattended. It does not get tired and it does not get distracted. That is the tradeoff: you write a policy once, and it enforces consistently from that point forward. ## Writing a policy you trust at full speed A policy that is right for a cautious, prompt-heavy workflow may not be strict enough for fully automated operation. When you remove prompts, consider whether your rules cover: - Sensitive filesystem paths (credentials, SSH keys, environment files). - Destructive shell commands (`rm -rf`, `truncate`, `dd`). - Network exfiltration (unexpected outbound hosts). - Git operations (force pushes, pushes to protected remotes). The default `file_policy` ruleset covers sensitive paths. For the rest, see [Policy recipes](/docs/policies/recipes) for patterns you can adapt. ## See also - [How it works](/docs/concepts/how-it-works): the tool-call boundary and how offline evaluation works. - [Run in CI](/docs/guides/ci): the companion guide for unattended agent runs in CI pipelines. - [Writing your first rule](/docs/policies/first-rule): add rules tailored to your specific workflow before skipping prompts.