How do permissions work in Codex?

HEAD
fad3d0fcodex: route thread/read persistence through thread store (#18352)
This post might have stale content, as HEAD is 329 commits ahead.

how do permissions work? "default permissions" sometimes asks me, sometimes it doesn't... what's the trick there?

Avatar of anthropic/claude-opus-4.6
Claude Opus 4.6
commented

Now I have the full picture. Here's the comprehensive answer:


##How Permissions Work in Codex

The permissions system has three layers that interact to decide whether you get prompted:

###Layer 1: Approval Policy (AskForApproval)

This is the top-level knob. It's determined by (in priority order):

  1. CLI override (--approval-mode)
  2. Config profile (approval_policy in your profile)
  3. Config file (approval_policy in codex.toml)
  4. Project trust level (auto-detected fallback)

The possible values are defined in codex-rs/protocol/src/protocol.rs:826:

PolicyBehavior
untrustedOnly "known safe" read-only commands auto-approved. Everything else prompts.
on-failure(deprecated) All commands auto-approved inside sandbox; prompt only if sandbox fails.
on-request(default) The model decides when to escalate. Normal commands run in the sandbox silently; only escalation requests prompt you.
granularFine-grained control over individual approval flows (sandbox, rules, skills, MCP, etc.)
neverNever prompt. Failures go straight back to the model.

The auto-detection fallback (codex-rs/core/src/config/mod.rs:1727):

if active_project.is_trusted() {
    AskForApproval::OnRequest
} else if active_project.is_untrusted() {
    AskForApproval::UnlessTrusted
} else {
    AskForApproval::default()  // OnRequest
}

So if you've explicitly marked a project as untrusted, the policy quietly becomes UnlessTrusted (prompts for almost everything). Otherwise it defaults to OnRequest.

###Layer 2: Sandbox Policy + default_permissions

The default_permissions field in your config points to a named permissions profile under the [permissions] table. This determines the sandbox boundaries — what files the model can read/write and whether network is allowed.

From codex-rs/config/src/config_toml.rs:112:

default_permissions = "workspace"

[permissions.workspace]
filesystem = { ... }
network = { ... }

The three UI presets (codex-rs/utils/approval-presets/src/lib.rs) map to:

PresetApprovalSandbox
Read Onlyon-requestReadOnly (restricted filesystem)
Defaulton-requestWorkspaceWrite (restricted filesystem)
Full AccessneverDangerFullAccess (unrestricted)

###Layer 3: The Actual Decision Logic — Why It Sometimes Asks and Sometimes Doesn't

This is where the "trick" is. The core logic lives in render_decision_for_unmatched_command() (line 547). Here's the decision tree for the default on-request policy:

  1. Is the command "known safe"? (e.g., cat, ls, grep, head, wc, find without -delete, read-only git subcommands, etc.) → ✅ Auto-approved, no prompt. (is_known_safe_command())

  2. Is the command flagged as "potentially dangerous"? (e.g., rm, destructive git commands, etc.) → ⚠️ Always prompts (unless policy is never). (command_might_be_dangerous())

  3. Otherwise, for non-dangerous, non-safe commands:

    • If filesystem sandbox is Unrestricted (Full Access mode): → ✅ Auto-approved — you chose to trust everything.
    • If filesystem sandbox is Restricted (Read Only or Default):
      • Did the model request sandbox_permissions: "require_escalated" or "with_additional_permissions"? → ⚠️ Prompts you — it's asking to break out of the sandbox.
      • Did the model use default sandbox (use_default)? → ✅ Auto-approved — the sandbox itself enforces restrictions, no need to prompt.
  4. Exec-policy rules (.rules files in ~/.codex/rules/): If a command matches an allow rule, it's auto-approved (and can even bypass the sandbox). If it matches a prompt rule, it always prompts. If it matches a forbidden rule, it's rejected outright.

###TL;DR — "The Trick"

With the default permissions (on-request + workspace-write sandbox):

ScenarioPrompted?Why?
cat foo.txt, ls, grepNoKnown-safe command
npm test, cargo build (normal)NoNot dangerous, sandbox handles it
Model asks to npm install with require_escalatedYesModel explicitly requests escalation (needs network)
rm -rf somethingYesFlagged as dangerous
Matches an allow rule in ~/.codex/rules/NoPre-approved by your rules
You previously approved with "for session"NoCached approval in ApprovalStore

The model itself decides when to set sandbox_permissions: "require_escalated" vs just running the command normally. The system prompt (on_request.md) instructs it to escalate when commands need network, write to unusual paths, or fail due to sandbox restrictions. When the model doesn't request escalation, and the command isn't dangerous, it just runs inside the sandbox — that's why you don't get prompted for most commands.


END OF POST

How do permissions work in Codex? — openai/codex