Permissions, sandboxes and approval modes across Claude Code, Codex and Cursor
Permissions and sandboxes in Claude Code, Codex and Cursor are three separate controls: an approval layer that decides whether an action runs, an OS sandbox that limits what a shell command can read and write, and a network allowlist that limits where it connects. An unattended run is safe only when all three are set, not the approval layer alone.
Your agent asked for approval forty times in one refactor, so someone added the bypass flag “just for today”. Two weeks later that flag sits in the CI job that triages public issues, the runner holds an npm publish token, and nobody can say which boundary would stop an injected curl. This page is for the developer configuring a session, the tech lead who owns shared settings, and the CTO who signs off on what unattended agents may reach. It is step 4 of the developer track, after repository context, because every later step, from worktrees to the issue-to-PR pipeline, assumes these boundaries exist.
What this permissions reference gives you
Section titled “What this permissions reference gives you”- A matrix of approval, sandbox, network and credential controls in Claude Code v2.1.283, Codex CLI 0.157.1 and Cursor.
- A posture table per risk class and per run type (interactive, background, CI).
- Configuration you can commit for all three tools, including a Codex GitHub Actions job.
- A canary test that proves the boundary holds, instead of trusting a settings file.
- Three copy-paste prompts and failure modes with recovery steps.
What are the three layers every agent run needs?
Section titled “What are the three layers every agent run needs?”The vendors use different words for the same three questions. A strong answer to one does not cover the others.
| Layer | The question it answers | Claude Code | Codex | Cursor |
|---|---|---|---|---|
| Approval | Does this action run, and who says yes? | Permission modes and allow/ask/deny rules | Approval policy and auto-review | Run modes and Auto-review |
| OS sandbox | What can a running command read and write? | Sandboxed Bash tool | Sandbox or permission profile | Sandbox for shell commands |
| Network egress | Where can a running command connect? | Sandbox proxy with a domain allowlist | Profile network setting | Not verified (see below) |
The approval layer judges the command string before it runs. The sandbox is enforced by the operating system on the running process. Anthropic’s sandboxing guide puts the difference plainly: the sandbox boundary “holds regardless of what the model chose to run and even if an allowed command does more than its name suggests”. A deny rule on Bash(curl *) stops curl, not python -c opening a socket; the sandbox stops both.
Outside all three sits the outer boundary: the laptop, container, VM or cloud environment the agent runs in, and its credentials. Choosing it is covered in ephemeral environments and the tool catalogue in agent sandboxes compared.
How do Claude Code, Codex and Cursor compare on permissions and sandboxing?
Section titled “How do Claude Code, Codex and Cursor compare on permissions and sandboxing?”Checked on 2026-09-26 against claude --help 2.1.283 and Anthropic’s permission, sandboxing and cloud-environment docs; codex --help 0.157.1, the rust-v0.157.1 source and the openai/codex-action README; and the @cursor/sdk 1.0.32 package. Cursor’s documentation site was unreachable from our environment on that date, so the Cursor column holds only what the SDK package or the 2026-08-28 docs check confirms.
| Control | Claude Code 2.1.283 | Codex CLI 0.157.1 | Cursor |
|---|---|---|---|
| Approval modes | default (shown as Manual), acceptEdits, plan, auto, dontAsk, bypassPermissions; Shift+Tab cycles | approval_policy: untrusted, on-request (default), granular, never. The -a flag accepts only on-request and never | A Run modes page under Agent security; reported as Auto-review (default), Allowlist and Run Everything (unverified, 2026-09-26) |
| A model approves instead of you | Auto mode: a classifier (Claude Sonnet 5 by default) reviews actions. It is the starting mode for interactive sessions from v2.1.283 (latest channel) | Auto-review: approvals_reviewer = "auto_review" in config, or --approve-for-me | Auto-review (SDK option local.autoReview) |
| Command rules | permissions.allow, ask, deny, evaluated deny, then ask, then allow | .rules files (experimental) decide which commands may run outside the sandbox | Hooks: JSON over stdio that “can observe, block, or modify behavior” |
| OS sandbox | Bash, PowerShell and Monitor commands. macOS Seatbelt, Linux and WSL2 bubblewrap; native Windows not supported | Every model-generated shell command. macOS Seatbelt, Linux and WSL2 bubblewrap; WSL1 rejected | SDK local.sandboxOptions; /sandbox in the CLI command list |
| Default write boundary | Working directory, a per-user temp directory, and directories added with --add-dir | :workspace profile writes the workspace; :read-only writes nothing | Not verified |
| Default network | No domains pre-allowed; first use of a host prompts, or goes to the classifier in auto mode | :workspace “does not grant network access” (codex-action README; confirmed locally, see the canary below) | Not verified |
| Egress allowlist | sandbox.network.allowedDomains, deniedDomains, strictAllowlist, managed allowManagedDomainsOnly | Named [permissions] profiles carry per-domain allow and deny (beta); legacy network_access is all or nothing | Not verified |
| Secrets in the environment | sandbox.credentials: deny or mask for files and variables; nothing is protected by default | shell_environment_policy: ignore_default_excludes defaults to true, so *KEY*, *SECRET*, *TOKEN* variables reach commands unless you set it to false | Not verified |
| Full bypass | --dangerously-skip-permissions; refused under root or sudo outside a recognised sandbox | --dangerously-bypass-approvals-and-sandbox | Not verified |
| Admin lock | Managed settings: disableBypassPermissionsMode, disableAutoMode, sandbox.failIfUnavailable, allowManagedDomainsOnly | requirements.toml: allowed_approval_policies, allowed_approvals_reviewers, allowed_permission_profiles, experimental_network, rules (checked against config_requirements.rs at rust-v0.157.1) | An mdm settings layer (SDK settingSources) |
Notes: the Claude Code sandbox covers shell commands only; the Read, Edit and Write tools obey permission rules, not the sandbox. Codex permission profiles (beta, from 0.138.0) and legacy --sandbox values “do not compose”, so pick one per configuration.
Which posture fits each risk class and run type?
Section titled “Which posture fits each risk class and run type?”A risk class belongs to a change, not a team. A run gets the posture of the highest class it can touch: a “docs only” task in a repository that holds billing code runs with the billing posture unless the sandbox denies it those paths. The tech lead owns this table per repository; the CTO or security owner signs off on the critical row and every exception.
| Risk class | Approval layer | Filesystem | Network | Credentials in the environment |
|---|---|---|---|---|
| Low (docs, internal tooling) | Reviewer model (auto mode, auto-review) | Workspace write | Package registries only | None beyond a read-only repository token |
| Medium (feature logic behind a flag) | Reviewer model, plus ask rules on git push and dependency changes | Workspace write, secret paths read-denied | Registries plus named internal hosts, strict allowlist | Masked or none |
| High (public API, tests, CI config) | Reviewer model for code; ask rules on .github/, test and CI config paths | Workspace write; CI config edits prompt | Strict allowlist, no wildcards on hosts that accept uploads | None |
| Critical (auth, money, migrations, secrets, the harness itself) | Manual or plan mode; never bypass | Workspace write inside a disposable container or VM | No egress beyond the registry mirror | No production credentials, ever |
A background or CI run has nobody to answer a prompt, so the run type limits which approval layer works:
| Run type | Approval layer that works | What replaces the human | Outer boundary |
|---|---|---|---|
| Interactive | Any mode; auto mode or auto-review for long tasks | You, for the prompts that remain | Your laptop, so the sandbox carries the weight |
| Background or cloud | Reviewer model; bypass only inside a disposable VM or container | The classifier or reviewer, plus the network level of the cloud environment | A vendor VM or your own runner |
| CI | An exact allowlist (dontAsk) or never with a profile | The allowlist itself: anything unlisted is denied | A disposable runner with scoped tokens |
Configure the interactive posture
Section titled “Configure the interactive posture”Commit the team baseline to the repository and put the non-negotiable parts in managed settings, which users cannot override. Managed policy covers distributing one policy across tools.
Project baseline in .claude/settings.json (medium class): sandbox on, no unsandboxed retry, limited egress, two tokens stripped from sandboxed commands, and a prompt for pushes and CI edits:
{ "permissions": { "deny": ["Read(./.env)", "Read(./.env.*)", "Bash(terraform apply *)"], "ask": ["Bash(git push *)", "Edit(/.github/**)"] }, "sandbox": { "enabled": true, "allowUnsandboxedCommands": false, "network": { "allowedDomains": ["registry.npmjs.org", "github.com", "api.github.com", "codeload.github.com"] }, "credentials": { "files": [ { "path": "~/.aws/credentials", "mode": "deny" }, { "path": "~/.ssh", "mode": "deny" } ], "envVars": [ { "name": "NPM_TOKEN", "mode": "deny" }, { "name": "GITHUB_TOKEN", "mode": "deny" } ] } }}Managed settings for the organization, delivered by MDM or server-managed settings, hold the keys a checked-out repository cannot set or relax:
{ "permissions": { "disableBypassPermissionsMode": "disable" }, "sandbox": { "enabled": true, "failIfUnavailable": true, "allowUnsandboxedCommands": false, "network": { "strictAllowlist": true, "allowManagedDomainsOnly": true } }}- The allowlist names exact GitHub hosts instead of
*.github.com, because the wildcard would also matchgist.github.comanduploads.github.com, both places an injected command could send data. - Project settings cannot start a session in
autoorbypassPermissions; those values are ignored there. Set the starting mode in user or managed settings. strictAllowlistdenies unlisted hosts instead of prompting, and has no effect in a repository’s settings files. It needs v2.1.219 or later.- On Linux and WSL2, install
bubblewrapandsocatfirst. Run/sandboxin a session: if it shows only a Dependencies tab, the sandbox is not running. /permissionslists every active rule and the file it comes from, and shows recent auto-mode denials.
User or project config.toml (medium class). The built-in :workspace profile writes only the workspace and grants no network. Setting ignore_default_excludes = false strips variables named like keys, secrets and tokens from shell commands:
default_permissions = ":workspace"approval_policy = "on-request"approvals_reviewer = "auto_review" # a reviewer agent decides escalations; default is "user"
[shell_environment_policy]ignore_default_excludes = falseAdmin constraints go in requirements.toml, which users cannot override. Its keys are not the config.toml names: it takes allowed_approval_policies, allowed_approvals_reviewers, allowed_permission_profiles, experimental_network and rules, among others (checked against config_requirements.rs at rust-v0.157.1). Writing approval_policy = "never" there constrains nothing.
- Do not add
sandbox_modeor--sandboxto a configuration that usesdefault_permissions; the two systems do not compose. /permissionsin the TUI shows and changes what Codex may do;/approveretries one auto-review denial.- When a task needs a package registry, install dependencies first or define a named
[permissions]profile with per-domain network entries (beta; check the schema in the Codex source).
Cursor documents approval and sandbox behaviour on its Run modes page under Agent security. We could not reach cursor.com on 2026-09-26 to check current mode names or defaults; read them off your own build before you set a team standard. Verified:
- Auto-review exists for local tool calls, and the SDK exposes it as
local.autoReview. - Sandboxing can be switched on per run through the SDK (
local.sandboxOptions), and/sandboxis listed among the Cursor CLI slash commands. - Hooks “can observe, block, or modify behavior”. Version a hook in the repository that blocks shell commands touching unapproved hosts or secret paths; take the event names from Cursor’s hooks page.
- Cloud Agents “run in isolated VMs in the cloud”, which moves the blast radius off the developer laptop.
Choose the most restrictive run mode your team can work in, turn the sandbox on, and run the canary below to see what it blocks.
Configure background and cloud runs
Section titled “Configure background and cloud runs”A background run cannot answer prompts, so the approval layer decides alone and the environment’s network level becomes the main egress control.
- Cloud sessions (claude.ai/code, routines) have a Network access level: None, Trusted (package registries, GitHub and cloud SDKs; the default), Full or Custom. GitHub traffic, enabled MCP connectors and the Anthropic API bypass it, so disable connectors a task does not need.
- Cloud sessions ignore
defaultMode: "bypassPermissions"and"dontAsk"from settings files, so a repository cannot start one in bypass mode. - Local background sessions started with
--bgare refused in bypass mode until you have accepted the bypass warning once interactively. In background sessions, strict sandbox mode also covers commands you type at the!prompt.
codex execruns non-interactively with the same profile and approval settings as the TUI. Put-abefore the subcommand:codex -a never exec …;codex exechas no-aof its own in 0.157.1.- Codex cloud tasks run “in isolated cloud environments” configured per environment (OpenAI docs, checked 2026-08-28). Treat the environment’s network setting as the egress control, and check it before connecting a repository that holds deployment credentials.
- Cloud Agents run in isolated VMs; give each VM its own scoped credentials, never a personal token.
- For local runs driven from code, the SDK makes the posture explicit. When
autoReviewor the sandbox is on, MCP server tool calls that would need interactive approval “fail closed”:
import { Agent } from '@cursor/sdk';
const MODEL_ID = process.env.CURSOR_MODEL_ID; // an id from Cursor.models.list()
const agent = await Agent.create({ apiKey: process.env.CURSOR_API_KEY, model: { id: MODEL_ID }, // pick one from Cursor.models.list() tools: ['read', 'grep', 'glob', 'edit', 'shell'], local: { cwd: process.cwd(), autoReview: true, sandboxOptions: { enabled: true }, settingSources: ['project', 'team', 'mdm'], },});
const run = await agent.send('Run the unit tests and fix the failing test in src/billing.');const result = await run.wait();console.log(result.status); // 'finished', 'error' or 'cancelled'if (result.status !== 'finished') process.exit(1);CURSOR_MODEL_ID holds a model ID from Cursor.models.list(); the SDK requires one for local agents. result.status is finished, error or cancelled. Checked against @cursor/sdk 1.0.32.
Configure CI runs
Section titled “Configure CI runs”In CI an agent meets untrusted text: issue titles, pull request bodies and diffs from forks. Give the job an exact allowlist, a workspace-only sandbox and a token scoped to its one task. The GitHub Actions hardening (pull_request_target, token permissions, OIDC) lives in agent identity, credentials and secrets.
dontAsk denies everything that would prompt, so the allowlist is the whole policy. --permission-prompts none makes sure nothing waits for an answer. --settings turns the sandbox on for this run and forbids the unsandboxed retry, and the --disallowedTools rule keeps the tests, which are the oracle, out of reach of Edit (adjust /tests/** to your layout, using the same path form as the other rules on this page):
# CI: run and fix tests, nothing elseclaude -p "Run the test suite and fix failing tests in src/. Do not edit tests." \ --permission-mode dontAsk \ --permission-prompts none \ --settings '{"sandbox":{"enabled":true,"allowUnsandboxedCommands":false}}' \ --allowedTools "Read" "Edit" "Bash(npm test)" "Bash(npm run lint)" \ --disallowedTools "Edit(/tests/**)"claude -p sessions start in Manual, so unlisted actions are denied even without --permission-mode. For a read-only triage job, --restricted removes the command-running tools and WebFetch unless --tools names them, and refuses bypassPermissions.
The openai/codex-action README recommends a permission profile over legacy sandbox flags, and its default safety-strategy: drop-sudo removes the runner user’s sudo before Codex starts:
- name: Run Codex uses: openai/codex-action@v1 with: openai-api-key: ${{ secrets.OPENAI_API_KEY }} permission-profile: ":workspace" safety-strategy: drop-sudo prompt: | Run the unit tests and fix failing tests in src/. Do not edit tests.Install dependencies in an earlier step: :workspace grants no network. Supplying both permission-profile and sandbox fails before Codex starts. GitHub-hosted Windows runners require safety-strategy: unsafe, so keep agent jobs on Linux or macOS. From a plain shell:
codex -a never exec -c 'default_permissions=":workspace"' \ "Run the unit tests and fix failing tests in src/. Do not edit tests."The verified route is the SDK script above, saved as run-agent.mjs and run from a CI step. It installs @cursor/sdk in an earlier step and reads its key from a secret and its model ID from a repository variable:
- run: npm install @cursor/sdk@1.0.32- name: Run Cursor agent run: node run-agent.mjs env: CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }} CURSOR_MODEL_ID: ${{ vars.CURSOR_MODEL_ID }}The script keeps sandboxOptions: { enabled: true } and exits non-zero when the run does not finish. Cursor also documents a headless CLI print mode (-p, --print) for GitHub Actions, checked on 2026-08-28; its permission flags were not verifiable here. Run the job on a disposable Linux runner with a repository-scoped token, and prove the boundary with the canary below before the job sees public input. The cross-tool CI setup is compared in headless agents in CI.
How do you prove the sandbox boundary holds?
Section titled “How do you prove the sandbox boundary holds?”A settings file is a claim. A canary is evidence: a command that must fail under the policy, run the way the agent runs. Run it when you set a posture, on every harness settings change, and after each tool upgrade.
-
Pick three probes for the posture: a write outside the workspace, a read of a secret path, and a connection to a host that is not on the allowlist.
-
For Codex, run the probes through
codex sandbox, which applies the same sandbox the agent gets. With codex-cli 0.157.1 on Linux, this run printedok, thenRead-only file systemfor/etc, then a refused connection forcurl:Terminal window codex sandbox -c 'default_permissions=":workspace"' -- sh -c \'echo ok > probe.txt && cat probe.txt; echo x > /etc/probe; curl -sS -m 5 https://example.com' -
For Claude Code and Cursor, run the canary prompt below in a session with the posture you ship. Each probe must be blocked or denied; for background and CI postures, an approval prompt counts as a failure.
-
Record the result in the evidence bundle of the pull request that changed the settings. The harness owner signs off on the canary output, not on the settings diff.
-
Re-run the canary in CI whenever
.claude/settings.json,config.tomlor a hook changes. Harness changes are critical class.
Copy-paste prompts for permissions and sandboxing
Section titled “Copy-paste prompts for permissions and sandboxing”What breaks in agent permissions and sandboxes, and how to recover
Section titled “What breaks in agent permissions and sandboxes, and how to recover”- The sandbox is not running, and nothing says so. By default, if bubblewrap or
socatis missing or the platform is native Windows, Claude Code warns and runs commands unsandboxed. Recovery: setsandbox.failIfUnavailable: truein managed settings, and on Ubuntu 24.04 or later add the AppArmor profile forbwrapthat Anthropic’s sandboxing guide gives. - The agent retries outside the sandbox. Claude Code may rerun a blocked command with
dangerouslyDisableSandbox, which goes through normal approval, so in auto mode the classifier may approve it. Recovery:allowUnsandboxedCommands: false, or an ask rule onBash(dangerouslyDisableSandbox:true). - A deny rule is bypassed by another spelling.
Bash(curl *)does not matchsh -c 'curl …'. Recovery: treat command rules as a first filter; the network allowlist is the egress control. - Project settings that do nothing.
autoorbypassPermissionsasdefaultMode,strictAllowlist, credentialmaskentries andfilesystem.disabledare ignored in a repository’s.claude/settings. Recovery: move them to user or managed settings. - An allowlisted host doubles as an exit. A wildcard on a host that accepts uploads or gists lets an injected command send data out through an approved domain; Claude Code’s own advisories list exfiltration through a pre-approved HuggingFace domain in WebFetch (Moderate, 2026-06-13); the same pattern applies to any allowlisted upload host. Recovery: allow exact hosts, prefer a registry mirror, and put
deniedDomainson upload endpoints. - Secrets ride along in the environment. Codex passes
*TOKEN*variables to commands unlessignore_default_excludes = false; Claude Code protects only what you list. Recovery: strip or mask them, and keep production credentials out of agent environments. - Codex rejects the flags.
codex -a untrustedand-a on-failurefail with “invalid value” in 0.157.1,codex exec -a neverfails with “unexpected argument”, and a profile plus--sandboxdoes not compose. Recovery:untrustedandgranulargo inconfig.toml; put-abeforeexec; pick profiles or legacy flags, not both. - A
-prun quietly skips blocked work. In headless Claude Code with auto mode, three consecutive classifier blocks or 20 in total pause auto mode; the action does not run and Claude keeps working. Recovery: never gate the job on the agent’s exit code; re-run tests and lint in a separate CI step and fail on those. - The agent itself has bugs. Claude Code’s security advisories include a sandbox escape via git worktree path confusion (High, 2026-06-25) and a workspace-trust bypass through a repository-controlled settings file (High, 2026-03-18). Recovery: stay on a supported channel, keep an outer container for critical class, and re-run the canary after each upgrade.
Where to go next with the agent harness
Section titled “Where to go next with the agent harness”- The harness overview places permissions among context, tools, hooks, skills and environments.
- The agent threat model shows which runs combine private data, untrusted input and an outbound path.
- Hooks as deterministic guardrails adds checks the approval layer cannot express.
- Ephemeral environments gives each run a disposable outer boundary.
- Background and cloud agents compared covers the vendors’ hosted environments.
- Next on the developer track: the artifact chain, which defines what each unattended run must leave behind as evidence.
Frequently asked questions
Is a permission mode the same as a sandbox?
No. A permission mode or approval policy decides whether an action runs and who approves it. An OS sandbox limits what a shell command can read, write and reach once it runs. Unattended runs need both, plus a network allowlist.
Which setting lets an agent run unattended safely?
None on its own. Pair a reviewer or allowlist approval layer (Claude Code auto or dontAsk mode, Codex auto-review or never) with a workspace-only sandbox, a network allowlist and no production credentials in the environment.
When is it acceptable to bypass all permission checks?
Only inside an external boundary you control, such as a disposable container or VM with no production credentials and restricted egress. Both vendors' bypass flags say the same in their help text.