Permissions
Control which tools can execute automatically, require confirmation, or are blocked entirely.
Overview
Permissions provide fine-grained control over tool execution. You can configure which tools are auto-approved (run without asking), which require user confirmation, and which are completely blocked.
Permissions are evaluated in this order: Deny → Allow → Ask. Deny patterns take priority, then allow patterns, and anything else falls through to the session's safety mode.
Safety Modes
Every session runs in a safety mode that decides what happens when no permission rule matched a tool call. The runtime labels each call safe (safe-listed shell command such as ls or git status, or a read-only-annotated tool), destructive (destructive shell command such as rm -rf, or a destructive-annotated tool), or unknown — and the mode gates on that label. Command classification applies to both shell and run_background_job:
| Mode | safe | destructive | unknown |
|---|---|---|---|
strict |
ask | ask | ask |
balanced |
allow | ask | ask |
restricted |
allow | deny | deny |
autonomous |
allow | allow | allow |
strictprompts for every tool call, read-only ones included. Only anallow:rule silences a prompt.balancedruns safe calls silently and asks about everything else.restrictedis the fail-closed profile for unattended/headless runs: safe calls run silently and everything else is denied without asking — the mode's fallback never prompts. Custom rules still win: anallow:rule can approve a destructive/unknown call, adeny:rule always blocks, and session-scopedask:rules still prompt (as can apreempt_yolohook). Restricted is defense in depth against unwanted tool calls, not a security boundary — for real isolation use sandbox mode.autonomousis the legacy--yolobehavior: everything runs. Onlydeny:rules, session-scopedask:rules,tool_guard, andpreempt_yolohooks still gate.
Pick a mode with the --safety flag (docker-agent run --safety balanced ...), the safety_policy field on session create (POST /api/sessions) or mid-session (PATCH /api/sessions/:id/safety-policy), or escalate directly from a confirmation prompt (B switches to balanced, A to autonomous; the restricted fallback never prompts, so the mode is only selected via flag/config/API). Sessions that never choose a mode keep the historical default: read-only tools auto-approve, everything else asks.
Declarative Safety Defaults
Safety modes can also be declared as defaults in YAML, at four scopes:
| Scope | Location | Owner |
|---|---|---|
| Alias | aliases.<name>.safety in ~/.config/cagent/config.yaml (or docker agent alias add ... --safety <mode>) |
User |
| Global settings | settings.safety in ~/.config/cagent/config.yaml |
User |
| Per-agent | agents.<name>.safety in the agent YAML |
Agent author |
| Config-wide | runtime.safety in the agent YAML |
Agent author |
# Agent YAML (author-declared defaults)
runtime:
safety: balanced # config-wide default for new sessions
agents:
root:
safety: strict # overrides runtime.safety for this agent
All four fields accept only the four canonical modes — strict, balanced, restricted, autonomous (yes, an author may declare autonomous) — and any other value fails loading with an error naming the field. The legacy spellings remain as aliases for autonomous: settings.YOLO, the alias yolo option, and the --yolo flag. When both are set at the same scope, safety wins over the legacy YOLO/yolo.
For a new root session the first source in this order wins:
- explicit
--safetyflag - explicit
--yoloflag - alias
safety/yolooption settings.safety/settings.YOLO(user config)- selected agent's
agents.<name>.safety runtime.safety- the historical default (read-only tools auto-approve, everything else asks)
Resuming a session never re-applies defaults: the stored mode is kept unless you pass an explicit --safety or --yolo flag for that run. Agent switches, handoffs, and delegated sub-agent sessions inherit the active session's mode rather than resetting it.
Sessions created through the API (POST /api/sessions) without a safety_policy receive the author-declared defaults (5–6) when their first run starts — the earliest point the agent configuration is loaded. If the server restarts before that first run, the session keeps the historical unset default (7).
Trust: author defaults never outrank you. runtime.safety and agents.<name>.safety are written by the agent's author — which may be a config you pulled from a URL or an OCI registry. They only fill the gap when you expressed no preference: any user-owned source (CLI flag, alias option, user settings) always takes precedence, and a resumed session keeps its stored mode. Still, an author default of autonomous means a fresh session runs every tool call unprompted — review third-party configs before running them, or pin your own floor with settings.safety / --safety.
Custom rules always win over the mode, with one asymmetry: ask: rules written in an agent's YAML (or global config) are agent-author advisories and yield to a user-chosen balanced/restricted/autonomous mode (under restricted they resolve to the mode's allow-or-deny verdict rather than introducing a prompt), while ask: rules granted at the session level (interactive "always ask" decisions, the session permissions API) always prompt.
Permission Levels
Permissions can be defined at two levels:
| Level | Location | Scope |
|---|---|---|
| Agent-level | Agent YAML config (permissions: section) |
Applies to that specific agent config |
| Global (user-level) | ~/.config/cagent/config.yaml under settings.permissions |
Applies to every agent you run |
Hooks follow the same user-config pattern: agent-level hooks live under agents.<name>.hooks, and global hooks live under settings.hooks. See Hooks.
Both levels use the same allow/ask/deny pattern syntax. When both are present, they are merged at startup -- patterns from both sources are combined into a single checker. See Merging Behavior for details.
Agent-Level Configuration
agents:
root:
model: openai/gpt-4o
description: Agent with permission controls
instruction: You are a helpful assistant.
permissions:
# Auto-approve these tools (no confirmation needed)
allow:
- "read_file"
- "read_*" # Glob patterns
- "shell:cmd=ls*" # With argument matching
# Ask when no deny or allow pattern matches (subject to the safety mode)
ask:
- "shell:cmd=git push*"
- "write_file:path=/home/user/important/*"
# Block these tools entirely
deny:
- "shell:cmd=sudo*"
- "shell:cmd=rm*-rf*"
- "dangerous_tool"
The three lists are evaluated in order deny → allow → ask. An ask: entry does not override a matching allow rule; narrow or remove the allow rule where confirmation is required. Team-level asks also yield to the active safety mode as described above.
Global Permissions
Global permissions let you enforce rules across all agents, regardless of which agent config you run. Define them in your user config file:
# ~/.config/cagent/config.yaml
settings:
permissions:
deny:
- "shell:cmd=sudo*"
- "shell:cmd=rm*-rf*"
allow:
- "read_*"
- "shell:cmd=ls*"
- "shell:cmd=cat*"
This is useful for setting personal safety guardrails that apply everywhere -- for example, always blocking sudo or always auto-approving read-only tools -- without relying on each agent config to include those rules.
Merging Behavior
When both global and agent-level permissions are present, they are merged into a single set of patterns before evaluation. The merge works as follows:
- Deny patterns from either source block the tool. A global deny cannot be overridden by an agent-level allow, and vice versa.
- Allow patterns from either source auto-approve the tool (as long as no deny pattern matches).
- Ask patterns from either source are considered only when no deny or allow pattern matches. They prompt in strict or legacy mode, but yield when the active mode is
balanced,restricted, orautonomous.
The evaluation order remains the same after merging: Deny > Allow > Ask > safety-mode fallback.
If your global config denies shell:cmd=sudo* and an agent config allows shell:cmd=sudo apt update, the deny wins. Deny patterns always take priority regardless of source.
Pattern Syntax
Permissions support glob-style patterns with optional argument matching:
Simple Patterns
| Pattern | Matches |
|---|---|
shell |
Exact match for shell tool |
read_* |
Any tool starting with read_ |
github_* |
Any GitHub MCP tool |
* |
All tools |
Argument Matching
You can match tools based on their argument values using tool:arg=pattern syntax:
permissions:
allow:
# Allow shell only when cmd starts with "ls" or "cat"
- "shell:cmd=ls*"
- "shell:cmd=cat*"
# Allow edit_file only in specific directory
- "edit_file:path=/home/user/safe/*"
deny:
# Block shell with sudo
- "shell:cmd=sudo*"
# Block writes to system directories
- "write_file:path=/etc/*"
- "write_file:path=/usr/*"
Colons inside argument values are preserved. Only the :key= token boundaries between
argument conditions split a pattern — colons that appear inside a value are treated as
ordinary characters and do not start a new condition. Check a tool’s actual argument names
(and whether they accept a string or a list) before writing an argument-matching pattern.
Multiple Argument Conditions
Chain multiple argument conditions with colons. All conditions must match:
permissions:
allow:
# Allow shell with ls in current directory
- "shell:cmd=ls*:cwd=."
deny:
# Block shell with rm -rf anywhere
- "shell:cmd=rm*:cmd=*-rf*"
Glob Pattern Rules
Patterns follow filepath.Match semantics with some extensions:
*— matches any sequence of characters (including spaces)?— matches any single character[abc]— matches any character in the set[a-z]— matches any character in the range
Matching is case-insensitive.
Trailing wildcards like sudo* match any characters including spaces, so sudo* matches sudo rm -rf /.
Decision Types
| Decision | Behavior |
|---|---|
| Allow | Tool executes immediately without user confirmation |
| Ask | User must confirm before tool executes (default) |
| Deny | Tool is blocked and returns an error to the agent |
Examples
Read-Only Agent
Allow all read operations, block all writes:
permissions:
allow:
- "read_file"
- "read_multiple_files"
- "list_directory"
- "directory_tree"
- "search_files_content"
deny:
- "write_file"
- "edit_file"
- "shell"
Safe Shell Agent
Allow specific safe commands, block dangerous ones:
permissions:
allow:
- "shell:cmd=ls*"
- "shell:cmd=cat*"
- "shell:cmd=grep*"
- "shell:cmd=find*"
- "shell:cmd=head*"
- "shell:cmd=tail*"
- "shell:cmd=wc*"
deny:
- "shell:cmd=sudo*"
- "shell:cmd=rm*"
- "shell:cmd=mv*"
- "shell:cmd=chmod*"
- "shell:cmd=chown*"
MCP Tool Permissions
Control MCP tools by their qualified names:
permissions:
allow:
# Allow all GitHub read operations
- "github_get_*"
- "github_list_*"
- "github_search_*"
deny:
# Block destructive GitHub operations
- "github_delete_*"
- "github_close_*"
Combining with Hooks
Permissions work alongside hooks. The evaluation order is:
- Run
tool_input_transformhooks — patch arguments before classification, guards, or permission checks - Run
tool_guardhooks — mandatory checks; deny is terminal and ask requires fresh confirmation (unless policy denies the call) - Run
preempt_yolopre_tool_use hooks — legacy mandatory checks, with their existing session-grant exception - Check deny patterns — if matched, tool is blocked
- Check allow patterns — if matched, tool is auto-approved
- Check ask patterns — if matched, the user is prompted directly, skipping the default
pre_tool_uselane - If no rule matched, apply the safety mode to the call's safety label — may auto-approve (or, under
restricted, deny) - On a mode "ask", run pre_tool_use hooks — hooks can allow, deny, or ask
- If no decision, ask user for confirmation
Default-lane hooks only see calls the mode routed to "ask"; they cannot override deny decisions or explicit ask: rules. If they rewrite arguments, mandatory checks and permission rules are evaluated again before execution. A new ask during this recheck requires fresh confirmation; earlier grants cannot bypass it. See tool phases.
Permissions are enforced client-side. They help prevent accidental operations but should not be relied upon as a security boundary for untrusted agents. For stronger isolation, use sandbox mode.