# 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

```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__<server>__<tool>`. 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).
