Managed permission profiles with non-cwd writable roots incorrectly tell the model that cwd is writable

Open 💬 1 comment Opened Aug 9, 2026 by Frefreak

What version of Codex CLI is running?

codex-cli 0.146.1, Homebrew installation

What subscription do you have?

Pro (Lite), 5×

Which model were you using?

gpt-5.6-sol

What platform is your computer?

Darwin 25.6.0 arm64 arm

What terminal emulator and version are you using?

tmux 3.6a

Codex doctor report

Not collected. This is a deterministic prompt-generation issue reproduced from the effective permission profile and verified against the rust-v0.146.1 source.

What issue are you seeing?

A managed permission profile that extends :read-only and grants write access exclusively to directories outside the current working directory generates contradictory model instructions.

For example, with a profile granting write access to /Users/me/shared, while cwd is /Users/me/project, Codex tells the model:

sandbox_mode is workspace-write: The sandbox permits reading files, and editing files in cwd and writable_roots.

It subsequently lists /Users/me/shared as a writable root and omits /Users/me/project.

The effective policy keeps /Users/me/project read-only. Consequently, an apply_patch targeting that directory correctly requests approval.

The approval behavior is correct. The generated instruction falsely grants cwd write access, encouraging the model to attempt writes that predictably trigger approval.

Relevant code

Release commit: 79b4f03d (rust-v0.146.1)

The primary classification occurs in sandbox_prompt_from_policy(), lines 193–206:

let writable_roots = file_system_policy.get_writable_roots_with_cwd(cwd);
if writable_roots.is_empty() {
    (SandboxMode::ReadOnly, None)
} else {
    (SandboxMode::WorkspaceWrite, Some(writable_roots))
}

Any restricted profile with at least one writable root is classified as WorkspaceWrite. This does not check whether any writable root contains cwd.

The selected workspace_write.md template, line 1 unconditionally says:

The sandbox permits reading files, and editing files in `cwd` and `writable_roots`.

The existing builds_permissions_from_profile test, lines 187–218 uses /tmp/repo as its writable root while cwd is /tmp. It expects the workspace-write label but does not assert whether the generated statement about cwd is accurate.

Runtime enforcement separately checks each patch target using can_write_path_with_cwd() in safety.rs, lines 135–203:

file_system_sandbox_policy.can_write_path_with_cwd(&abs, &native_cwd)

An out-of-root target becomes SafetyCheck::AskUser, which is converted into ExecApprovalRequirement::NeedsApproval in apply_patch.rs, lines 34–69.

What steps can reproduce the bug?

Configure a named profile whose writable root does not contain the working directory:

default_permissions = "manual"
approval_policy = "on-request"

[permissions.manual]
extends = ":read-only"

[permissions.manual.filesystem]
"/Users/me/shared" = "write"
  1. Start Codex with /Users/me/project as cwd.
  2. Ask Codex to report its injected filesystem permission instructions.
  3. Observe that it reports sandbox_mode = workspace-write and claims cwd is editable.
  4. Observe that the listed writable roots contain /Users/me/shared and omit /Users/me/project.
  5. Ask Codex to create a file in /Users/me/project using apply_patch.
  6. Observe the approval request.

What is the expected behavior?

Generated permission instructions should accurately describe the effective policy.

For arbitrary managed profiles, suitable wording would be:

The sandbox permits writes within the writable roots listed below. The current working directory is writable only when covered by one of those roots.

Alternatively, prompt classification could distinguish a traditional workspace-write profile from a restricted managed profile having unrelated writable roots.

The existing enforcement and approval behavior should remain unchanged.

Additional information

I searched open and closed issues and pull requests using the exact generated wording, sandbox_prompt_from_policy, workspace-write, writable roots, managed permission profiles, and extends = ":read-only". I found related permission-profile reports but no exact duplicate.

The closest reports concern read-scope expectations (#23911), ignored custom profiles (#28281), actual temporary-directory write enforcement (#32395), and an alleged out-of-root apply_patch bypass (#31434).

View original on GitHub ↗

1 Comment

jdcodes1 · 9 days ago

Verified on current main (1f41cc5d92): unchanged. sandbox_prompt_from_policy still classifies any restricted profile with ≥1 writable root as WorkspaceWrite without checking whether any root contains cwd (https://github.com/openai/codex/blob/1f41cc5d92/codex-rs/prompts/src/permissions_instructions.rs#L193-L206), and the workspace_write.md template still unconditionally asserts editing is permitted "in cwd and writable_roots". Your analysis of the enforcement side is also right — can_write_path_with_cwd keeps the actual policy correct, so this is purely a prompt/reality divergence that manufactures predictable approval churn (the model is told cwd is writable, tries, and gets bounced every time).

Fix is small and local: compute cwd_writable = writable_roots.iter().any(|root| root.contains(cwd)) in the same function and select between two template variants — the current text when true, and "editing files only in writable_roots; cwd is read-only" when false (plus listing cwd's status explicitly). The existing builds_permissions_from_profile test is the right place for the regression assertion, since as you note it already constructs exactly this shape (/tmp/repo root, /tmp cwd) and just doesn't check the sentence.