Permissions
Permissions are how you keep Anode useful without giving it the keys to everything. Every tool call is checked before it runs.
How It Works
Section titled “How It Works”Read operations run automatically. File mutations require confirmation unless
policy or unrestricted approval allows them. Headless execution defaults to
ask; the TUI starts in auto, which auto-allows non-destructive shell
commands but still prompts for writes and destructive-looking commands.
You control behavior with three layers:
- approval mode;
- JSON permission rules;
- named presets.
Default Behavior
Section titled “Default Behavior”Out of the box, Anode asks before doing anything destructive:
- Auto-allowed - reading files, searching code, browsing the repo.
- Requires confirmation - creating files, editing files, and shell commands that are not allowed by the active approval mode or policy.
You see a confirmation prompt with the tool name and arguments. Press y to approve or n to reject.
Permission Levels
Section titled “Permission Levels”Each built-in tool has an assigned permission level.
| Level | Description | Example Tools |
|---|---|---|
auto_read | Always allowed. Read-only operations. | read, multi_read, finder, glob, grep, chart, todo_read |
external_read | External service or delegated read-only investigation. Allowed by default. | web_search, oracle, code_review |
state_write | Local agent state mutations. Allowed by default. | todo_write, task_list, send_message |
confirm_execute | Requires confirmation. Shell or executable execution. | bash, most toolbox tools |
confirm_write | Requires confirmation. File mutations. | edit_file, create_file, apply_patch |
delegated | Delegated to subagent or skill. | task, skill, handoff |
interactive | Requires user interaction. | ask_user |
unavailable | Tool is disabled. | - |
Approval Modes
Section titled “Approval Modes”Control how Anode handles confirmation prompts.
| Mode | Behavior |
|---|---|
ask | Ask for confirmation on confirm_execute and confirm_write tools. This is the headless default. |
auto | Auto-allow non-destructive shell commands. Shell commands that look destructive still ask. File writes still ask unless a permission rule explicitly allows them. |
unrestricted | Never ask for confirmation. Everything runs immediately. yolo is an alias for unrestricted. |
Switch from the CLI:
anode -x --approval auto "run tests and summarize failures"anode execute "refactor the auth module" --approval unrestrictedSwitch in the TUI:
/approvalCycles through ask -> auto -> unrestricted.
Permission Rules
Section titled “Permission Rules”Write fine-grained rules to allow, reject, or gate specific tool calls. Rules are JSON arrays of objects.
Add rules from the CLI:
anode permissions add allow readanode permissions add reject bash --match 'command=rm -rf *' --message "No destructive rm"anode permissions add ask edit_file --match 'path=*.lock' --workspaceanode permissions add delegate bash --to reviewer --context child_runpermissions add prepends the new rule so it takes priority. By default it
updates the user config at ~/.config/anode/config.json without round-tripping
provider credentials discovered from environment variables. Use --workspace
to write .agents/permissions.json.
Rule structure
Section titled “Rule structure”{ "tool": "bash", "matches": { "command": "rm -rf *" }, "action": "reject", "context": "main_run", "message": "Destructive rm is not allowed"}| Field | Type | Description |
|---|---|---|
tool | string | Glob pattern matching the tool name. Default: "*". |
matches | object | Key-value pairs matched against call arguments. Supports string globs and array alternatives. |
action | string | allow, reject, ask, or delegate. |
context | string | main_run or child_run. Omit to match both. |
message | string | Message shown when a reject, ask, or delegate rule fires. |
to | string | Delegate target label for delegate rules. |
In normal engine-backed runs, delegate routes the matching call through a
child run using the profile named by to. Direct tool runners that do not wire a
delegation handler fail clearly instead of running the original call.
Glob matching
Section titled “Glob matching”The tool field and values in matches support glob patterns.
[ { "tool": "bash", "matches": { "command": "git *" }, "action": "allow" }, { "tool": "mcp__*", "action": "ask" }, { "tool": "edit_file", "matches": { "path": "*.lock" }, "action": "reject", "message": "Do not modify lockfiles" }]Array alternatives in matches
Section titled “Array alternatives in matches”Use an array to match any of several values:
{ "tool": "bash", "matches": { "command": ["npm test", "npm run lint", "npm run build"] }, "action": "allow"}Rule Priority
Section titled “Rule Priority”Rules are evaluated in priority order. The first matching rule wins.
| Priority | Source | Path |
|---|---|---|
| 1 (highest) | User config | ~/.config/anode/config.json -> permissions array |
| 2 | Project config | .agents/permissions.json |
| 3 | Project config fallback | .anode/permissions.json, used only when .agents/permissions.json is absent or unreadable |
| 4 (lowest) | Preset rules | From the active permissionPreset |
Place your most specific rules in user config. Place shared team rules in .agents/permissions.json and commit them to the repo.
Named Presets
Section titled “Named Presets”Set a preset in ~/.config/anode/config.json:
{ "permissionPreset": "careful"}unrestricted
Section titled “unrestricted”Allow everything. No confirmation prompts.
[ { "tool": "*", "action": "allow" }]careful
Section titled “careful”Reject destructive commands. Ask before compound, redirected, backgrounded, or command-substituted shell commands. Allow read-only git commands, common read-only shell inspection commands, and read/search helper tools.
[ { "tool": "bash", "matches": { "command": ["rm -rf*", "rm -fr*", "rm -Rf*", "rm -r -f*", "rm -f -r*", "rm --recursive --force*", "rm --force --recursive*"] }, "action": "reject" }, { "tool": "bash", "matches": { "command": ["git reset --hard*", "git reset * --hard*"] }, "action": "reject" }, { "tool": "bash", "matches": { "command": ["git push --force*", "git push * --force*", "git push -f*", "git push * -f*"] }, "action": "reject" }, { "tool": "bash", "matches": { "command": ["git clean -*f*", "git clean * -*f*"] }, "action": "reject" }, { "tool": "bash", "matches": { "command": "drop table*" }, "action": "reject" }, { "tool": "bash", "matches": { "command": "drop database*" }, "action": "reject" }, { "tool": "bash", "matches": { "command": ["*;*", "*&*", "*||*", "*|*", "*>*", "*<*", "*$(*", "*`*", "*\n*"] }, "action": "ask" }, { "tool": "bash", "matches": { "command": "git diff*" }, "action": "allow" }, { "tool": "bash", "matches": { "command": "git log*" }, "action": "allow" }, { "tool": "bash", "matches": { "command": "git status*" }, "action": "allow" }, { "tool": "bash", "matches": { "command": "git show*" }, "action": "allow" }, { "tool": "bash", "matches": { "command": ["ls", "ls *", "pwd", "pwd *", "cat", "cat *", "head *", "tail *", "grep *", "rg *", "wc *"] }, "action": "allow" }, { "tool": "read", "action": "allow" }, { "tool": "finder", "action": "allow" }, { "tool": "glob", "action": "allow" }, { "tool": "grep", "action": "allow" }]strict
Section titled “strict”Ask for confirmation on every tool call.
[ { "tool": "*", "action": "ask" }]Test A Decision
Section titled “Test A Decision”Verify how your policy handles a specific tool call before running it for real.
# Test a bash commandanode permissions test bash --command "rm -rf /tmp/build"
# Test a file editanode permissions test edit_file --path "src/main.go"
# Test in child_run contextanode permissions test bash --command "npm test" --context child_run
# Test a rejected preset without editing configanode permissions test bash --preset careful --command "rm -rf tmp"
# Test an MCP toolanode permissions test mcp__github__create_issue
# Test with arbitrary argumentsanode permissions test bash --arg timeout_ms=5000 --command "make build"The shorthand form anode permissions <tool> accepts the same test flags.
Without a tool name, anode permissions lists rules and rejects test-only flags
instead of ignoring them.
The output shows which rule matched, the action taken, and the message (if any).
MCP And Toolbox Tools
Section titled “MCP And Toolbox Tools”MCP tools (mcp__<server>__<tool>) and toolbox tools (tb__<name>) pass through the same permission policy as built-in tools. Write rules targeting their full names:
[ { "tool": "mcp__github__*", "action": "allow" }, { "tool": "tb__deploy", "action": "ask", "message": "Confirm before deploying" }, { "tool": "mcp__*", "action": "reject", "context": "child_run", "message": "MCP tools blocked in subagents" }]Subagents
Section titled “Subagents”Subagents (spawned via the task tool) run in child_run context. Use the context field to scope rules to subagents only.
[ { "tool": "bash", "action": "reject", "context": "child_run", "message": "Subagents cannot run shell commands" }, { "tool": "edit_file", "matches": { "path": "migrations/*" }, "action": "reject", "context": "child_run", "message": "Subagents cannot modify migrations" }]Rules without a context field match both main_run and child_run.
Keep Going
Section titled “Keep Going”- Configuration - config file locations and the full settings reference.
- Providers - set up the LLM providers that power your agent.
- Tools - the built-in tools that permissions govern.
- Profiles - bind tool allowlists and read-only posture to agent profiles.