Permissions, sandboxing & approval modes
What permissions are
Section titled “What permissions are”Every CLI in scope has some way of deciding three things, on every action the agent attempts: do it without asking, ask me first, or refuse outright. The shapes are wildly different:
- Claude Code: rule-based allow/ask/deny per tool, layered with managed policy.
- Codex: OS-level sandbox tiers combined with approval policies.
- opencode: per-tool allow/ask/deny, scoped per primary agent.
- Cursor: auto-run modes (Always ask / Auto-run in sandbox / Always run everything) plus command allow/denylists; OS-level Agent Sandbox on macOS.
- Copilot: per-call tool approval in VS Code Chat, with
chat.permissions.defaultand per-category auto-approve knobs; optional shell sandbox; org-wide content exclusions.
Same job — decide what the agent can do unattended — different mental models, different vocabulary, different layers.
Why you’d want one
Section titled “Why you’d want one”You’re two settings into the agent’s lifecycle and you’ve already hit the wall. Either it pops up a confirmation dialog on every single command — and you spend the session pressing y, y, y until you stop reading what you’re approving — or you’ve turned approvals off and now there’s nothing between the agent and rm -rf node_modules in the wrong directory.
There’s a real range of actions the agent does in a session: reading a file (boring, always fine), running tests (boring, always fine), editing your code (usually fine), running a one-off shell command (depends on the command), pushing to remote (you definitely want to know), chmod-ing system files (probably not). Treating all of those as one “do you approve?” question is what produces both fatigue and accidents.
That’s the gap permissions close. Decide once, declaratively: which tools are always fine, which always need you in the loop, which are flatly forbidden. Then the agent runs unattended through the boring 80% and only interrupts you for the genuinely risky 20% — and the truly dangerous things just can’t happen, even if the agent decides they’re a good idea.
Real-world examples of permission rules people actually set:
- Always allow — reads (Read, Grep, Glob), test runs (
pnpm test,pytest), formatters (prettier,ruff), git status/log/diff. - Ask first — any
git push, anygit rebase, deletions, anything touchingnode_modulesorpackage-lock.json,npm install/pip install. - Always deny —
rm -rf, edits to.envor secrets, writes outside the project root,sudo,chmod, pushing tomaindirectly. - Scoped allow patterns —
Bash(npm run *)allowed,Bash(npm install *)asked. Same tool, different command shapes. - Per-agent profiles (opencode) — your
planagent is read-only no matter what; yourbuildagent has full edit access; a customsecurity-reviewagent has read + a narrow shell allowlist. - Org-wide policy — managed config forbids any tool that can touch
prodfor everyone in the team, regardless of what they set locally.
The test: if you’ve ever caught yourself approving a command without actually reading it, your permission rules are too tight. If you’ve ever discovered the agent did something you wouldn’t have approved, they’re too loose.
Why this and not…
Section titled “Why this and not…”| You want to… | Reach for | Not |
|---|---|---|
| Decide broadly what the agent can do unattended | Permissions | Hooks |
| Block one specific command pattern with logic (regex against args, conditional logic) | Hook on PreToolUse | Permissions alone |
| Take the agent fully read-only for one session | Plan mode | Toggling every permission |
| Restrict a worker subagent’s tools | Subagent tools: field | Session-wide permissions |
| Enforce a rule across the whole org | Managed/policy config | Per-user settings |
Permissions are coarse and declarative — “Bash is ask, Read is allow.” Hooks are fine and procedural — “if the bash command matches git push.*main, block it and log it.” Use permissions for the policy; reach for hooks when the policy needs logic.
How it works in each tool
Section titled “How it works in each tool”Permission rules live in settings.json (project, user, or managed). Each rule targets a tool with allow / ask / deny:
{ "permissions": { "allow": ["Bash(npm run test:*)", "Read(src/**)"], "deny": ["Bash(rm:*)", "Edit(.env)"], "ask": ["Bash(*)"] }}Layering: managed policy beats user beats project. An org admin can deny tool patterns globally; project settings can broaden within those limits.
Plan mode (Shift-Tab) gates all mutations as a posture — see Plan mode.
Claude Code does not impose OS-level process sandboxing; permissions are enforced at the tool-dispatch layer.
Codex separates what the agent is allowed to touch (sandbox) from when it asks before acting (approval policy). The two combine.
Sandbox modes (--sandbox):
read-only— agent can read but cannot edit files or run commandsworkspace-write— agent can edit files inside the workspace and run sandboxed commandsdanger-full-access— no sandboxing (use deliberately)
Approval policies (--ask-for-approval):
untrusted— ask for every actionon-request— agent decides when to asknever— never ask
on-failure is deprecated.
Common combinations:
- Plan-like behaviour:
--sandbox read-only --ask-for-approval untrusted - YOLO mode:
--sandbox danger-full-access --ask-for-approval never(only in throwaway environments)
Permissions are per-tool per-agent. Each primary agent (build, plan, custom) gets its own permission map:
permission: read: allow edit: ask bash: ask glob: allow grep: allow list: allow task: allowSwitching primary agents (Tab between build and plan) effectively switches permission policies — plan typically has edit: ask or edit: deny while build has edit: allow.
Wildcards * and ? are supported in tool patterns (but not ** recursive globs). Evaluation is last-match-wins — a common pattern is to put the catch-all "*" rule first and put more specific rules after it.
opencode itself does not ship a built-in OS-level sandbox; for stronger isolation, run the CLI inside a container (Docker, Incus, etc.) as a wrapper layer.
Cursor 2.0 introduced a real Agent Sandbox on macOS — auto-run commands execute inside an OS-level sandbox by default. Linux/Windows sandbox parity unverified as of 2026-05-21.
Three auto-run modes, set in Cursor Settings or via /auto-run in the CLI:
- Always ask — manual approval per command.
- Auto-run in sandbox — commands run inside the sandbox without prompting; commands needing network or filesystem outside the sandbox fall through to an allowlist.
- Always run everything — the historical “YOLO mode,” no gating.
There are command allowlist and denylist fields for fine-grained control. Two caveats worth flagging honestly: community reports note that “Auto-run in sandbox” can silently bypass the explicit allowlist in some builds, and the denylist has historically been bypassable via && chaining. Treat the allowlist/denylist as advisory and the sandbox as the load-bearing control.
Cloud Agents run inside isolated VMs in Cursor’s cloud — full sandbox-by-construction, with the local-machine threat model not applicable. Environment is reproducible from .cursor/environment.json (Dockerfile supported).
VS Code Chat (Agent mode) is the primary surface. Tool approval prompts fire per call by default.
Approval knobs in settings.json:
chat.permissions.default—default | autoApprove | autopilotchat.tools.terminal.autoApprove— terminal commandschat.tools.edits.autoApprove— file editschat.tools.global.autoApprove— global auto-approve (VS Code docs flag this as disabling most safety)
Sandboxing for agent shell commands via chat.agent.sandbox.enabled (macOS/Linux only).
Content exclusions configured org-wide on github.com block Copilot from indexing or reading specified paths across all surfaces — repo-, org-, or enterprise-scoped.
MCP allowlists are policy controls:
chat.mcp.access,chat.mcp.discovery.enabled,chat.mcp.apps.enabledgate which MCP servers can be added- Business/Enterprise admins set org-level MCP policy on github.com
Org-level controls (Business/Enterprise) also cover model allow/deny lists and extension allow/deny.
Coding Agent and Copilot CLI have their own permission models (GitHub Actions sandbox with built-in firewall; --allow-tool / --deny-tool flags respectively) — out of scope here.
Comparison
Section titled “Comparison”| Aspect | Claude Code | Codex | opencode | Cursor | Copilot |
|---|---|---|---|---|---|
| Primary axis | Allow/ask/deny per tool | Sandbox tier × approval policy | Per-tool per-agent | Auto-run mode + allow/denylist | Per-call approval + auto-approve knobs |
| OS-level sandbox | No | Yes (read-only / workspace-write / danger-full-access) | No (wrap in a container if you need it) | Yes (Agent Sandbox, macOS; other OSes unverified) | Optional (chat.agent.sandbox.enabled, macOS/Linux) |
| Approval modes | ask rules | untrusted / on-request / never | allow / ask / deny | Always ask / Auto-run in sandbox / Always run everything | default / autoApprove / autopilot |
| Org/managed override | Yes (managed settings.json) | Yes (managed config) | Yes (managed config) | Yes (Team / Enterprise) | Yes (Business / Enterprise; content exclusions, MCP policy) |
| Evaluation order | Deny > Ask > Allow | Sandbox blocks first, then approval gate | Last match wins | Sandbox first, then allow/denylist (with caveats) | Per-call gate unless auto-approve set |
| Per-agent profile | No (single profile per session) | No (single mode per session) | Yes (per primary agent) | — | — |
| “YOLO mode” | Allow Bash(*) + Edit(*) etc. | danger-full-access + never | All tools allow | ”Always run everything” | chat.tools.global.autoApprove |
Translation: “make it stop asking”
Section titled “Translation: “make it stop asking””| Tool | How |
|---|---|
| Claude Code | Add allow rules for the tool pattern, or move Bash(...) out of ask. |
| Codex | --ask-for-approval never (and pick a sandbox you trust). |
| opencode | Set the relevant tool to allow on the active primary agent. |
| Cursor | Switch auto-run mode to “Auto-run in sandbox” (or “Always run everything,” with the usual warning). |
| Copilot | Set chat.permissions.default to autoApprove, or flip specific chat.tools.*.autoApprove keys. |
Name collisions
Section titled “Name collisions”- “Sandbox” is a real OS-level isolation tier in Codex; in Claude Code and opencode the word is used loosely (or refers to Docker).
- “Approval” in Codex is the frequency policy (ask/never); in Claude Code it’s the result of an
askrule. Same word, different layer. - Codex’s
on-failureapproval mode is deprecated — don’t recommend it.