← All docs

The input schema

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

{
  "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:

FieldTypeDescription
commandstringThe shell command string the agent wants to run.

Example:

{
  "hook_event": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "cat ~/.ssh/id_rsa"
  }
}

Rule fragment:

input.tool_name == "Bash"
input.tool_input.command  # the command string

File tools

ToolRelevant tool_input field
Writefile_path
Editfile_path
Readfile_path (also accepted as path)
Renameold_path
NotebookEditnotebook_path

Example rule blocking writes to sensitive paths:

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__<server>__<tool>. Match on input.tool_name using startswith or regex.match:

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:

# 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:

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:

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.