|
| 1 | +# Approvals and permissions |
| 2 | + |
| 3 | +Every code action, shell command, and tool call requires approval before execution, unless a permission rule pre-approves it. This page explains the approval model: what gets approved, who decides, and why permission matching works the way it does. |
| 4 | + |
| 5 | +## The approval model |
| 6 | + |
| 7 | +A code action is approved as a whole before it runs. Shell commands and programmatic tool calls inside it are then intercepted during execution and approved individually. This two-level model means a single approved code action does not grant blanket permission to everything it does: each side effect surfaces as its own decision point. |
| 8 | + |
| 9 | +Interception happens with full runtime information. Python variables in shell commands are resolved before the approval request, so the user approves the actual command values, not templates. Composite shell commands joined with `&&`, `||`, `|`, or `;` are decomposed into individual sub-commands, each approved separately, so a harmless prefix cannot smuggle in a destructive suffix. `%%bash` scripts are approved as a whole. |
| 10 | + |
| 11 | +The `tool_call` type of an [`ApprovalRequest`][freeact.ApprovalRequest] determines what is being approved: |
| 12 | + |
| 13 | +| `tool_call` type | Trigger | |
| 14 | +|---|---| |
| 15 | +| [`CodeAction`][freeact.CodeAction] | Code action containing Python code and shell commands to execute | |
| 16 | +| [`ShellAction`][freeact.ShellAction] | Shell command (`!cmd`) or shell script (`%%bash`) intercepted during code action execution | |
| 17 | +| [`GenericCall`][freeact.GenericCall] | Programmatic tool call (intercepted during code action execution) or JSON tool call | |
| 18 | +| [`FileRead`][freeact.FileRead], [`FileWrite`][freeact.FileWrite], [`FileEdit`][freeact.FileEdit] | Filesystem operation via built-in MCP server | |
| 19 | + |
| 20 | +`GenericCall` covers both call styles; its `ptc` field is `True` for programmatic tool calls, so applications can match `GenericCall(ptc=True)` to treat them differently from JSON tool calls. |
| 21 | + |
| 22 | +## Who decides |
| 23 | + |
| 24 | +The agent itself is policy-free. It yields [`ApprovalRequest`][freeact.ApprovalRequest] events from its [event stream](runtime.md#events) and suspends execution until the application calls `approve()`. Calling `approve(True)` executes the action; `approve(False)` rejects it and ends the current agent turn. A rejection is signaled structurally: the final [`CodeExecutionOutput`][freeact.CodeExecutionOutput] has `approval_rejected=True`. |
| 25 | + |
| 26 | +The embedding application decides how requests are resolved. The CLI prompts the user interactively. The [SDK tutorial](../getting-started/sdk-tutorial.md) auto-approves everything. Applications that want stored rules use [`PermissionManager`][freeact.PermissionManager], the same utility the CLI uses internally; the agent never reads `permissions.toml` itself. See [Manage permissions](../guides/permissions.md) for both the interactive and the programmatic workflow. |
| 27 | + |
| 28 | +Two safeguards prevent approval requests from hanging forever: if the consumer abandons the stream while an approval is pending, the request is resolved as rejected, and with a configured `approval_timeout`, an unresolved request is rejected when the timeout expires. |
| 29 | + |
| 30 | +Subagent actions go through the same mechanism. Events from subagents flow through the parent agent's stream, with `agent_id` identifying the source, so the application applies one approval policy to the whole agent tree. |
| 31 | + |
| 32 | +## Why two tiers |
| 33 | + |
| 34 | +Permission rules are organized into `allow` and `ask` tiers, evaluated **ask then allow**: if a rule matches both tiers, the user is prompted. This order makes `ask` rules a safety net. A broad convenience rule like `allow git *` cannot silently cover a pattern the user wants to always confirm, such as `ask rm *`. Each tier supports an **always** scope (persisted to `permissions.toml`) and a **session** scope (in-memory, cleared when the session ends), separating durable policy from one-off trust. |
| 35 | + |
| 36 | +## The path matching model |
| 37 | + |
| 38 | +Rules for filesystem operations match on paths, and the matching is designed around the working directory as the trust boundary. Paths are normalized before matching: absolute paths under the working directory become relative, paths outside stay absolute. A relative pattern like `src/**` can therefore only ever match files inside the workspace, regardless of how the agent spells the path. Granting access outside the workspace requires an explicit absolute pattern with a leading `/`. Within patterns, `*` stays inside a single directory while `**` crosses directory boundaries, so a rule scoped to one directory does not silently extend to its subtree. See [Permission rules](../reference/permission-rules.md) for the exact syntax. |
0 commit comments