`cwd` in `UnifiedExecApprovalKey` makes `ApprovedForSession` grants silently directory-scoped
π€
What happens
when you approve a shell command "for this session," codex asks again the next time the model runs the same command from a different working directory. the session is still alive β no restart, no rotation β but the grant is effectively directory-scoped, not command-scoped.
Root cause
UnifiedExecApprovalKey (codex-rs/core/src/tools/runtimes/unified_exec.rs ~line 86) includes a cwd field:
pub struct UnifiedExecApprovalKey {
pub environment_id: String,
pub command: Vec<String>,
pub cwd: PathUri, // <-- this
pub tty: bool,
pub sandbox_permissions: SandboxPermissions,
pub additional_permissions: Option<AdditionalPermissionProfile>,
}
with_cached_approval looks up approval decisions by this full key. so if the model runs cd some/subdir mid-turn and then issues the same shell command, the cwd in the key differs from the one recorded at approval time β cache miss, re-ask.
"allow for session" stores ReviewDecision::ApprovedForSession in SessionServices.tool_approvals (session.rs, initialized at Session::new). because the session itself is stable (same process, same ACP session id, no rotation), the grant is present β the lookup just misses it every time the directory changes.
Minimal repro
- start a codex session
- ask the model to run any shell command (e.g.
ls) - approve it "for this session"
- ask the model to
cdsomewhere and then run the samelsagain - codex asks for approval again
What i checked / ruled out
- session rotation β i logged ACP session ids across turns; the session id is stable between the first approval and the re-ask. no
session/newfires between them. - store reset β
ApprovalStoreis only initialized atSession::new; there's no code path that clears it mid-session. - "allow commands starting withβ¦" β this option writes to
~/.codex/rules/default.rulesviaexec_policy.append_amendment_and_update, which is disk-persistent and cwd-independent. it works correctly across directory changes. the bug is specific toApprovedForSession.
Proposed fix
for session-scoped grants specifically, cwd should not be part of the lookup key. the user is saying "i trust this command for the rest of this session," not "i trust this command from this directory for the rest of this session." two options i can think of:
- when storing / looking up a
ApprovedForSessiondecision, use a reduced key β{environment_id, command}only β and ignorecwd,tty,sandbox_permissions. - store session grants in a separate map keyed on just
{environment_id, command}, checked before the full-key map.
happy to look at a PR if the fix shape is agreed on.
1 Comment
Verified on
main(1f41cc5d92):UnifiedExecApprovalKeystill carriescwd: PathUri(https://github.com/openai/codex/blob/1f41cc5d92/codex-rs/core/src/tools/runtimes/unified_exec.rs#L89-L96), soApprovedForSessiongrants are directory-scoped exactly as you traced β and this sits right next to #38328, where the same cache keys the exact argv instead of the executable. Together they explain most "codex keeps re-asking" reports: the session-grant key is stricter than what the approval dialog implies on two independent axes.On the fix, dropping
cwdfrom the key wholesale is almost right but has one security caveat worth handling: for commands whose risk is cwd-relative (rm -rf .,git clean -fd, anything acting on "here"), a grant issued in/tmp/scratchsilently extending to the repo root is a real widening. A shape that keeps the ergonomics without that edge: key on (environment, command, tty, permissions) as you propose, but keepcwdin the stored decision and re-prompt only when the new cwd escapes the workspace roots the original grant was made under β i.e., scope grants to the sandbox boundary rather than the literal directory. That matches what "for this session" plausibly means to users (this project, this session) while not letting a scratch-dir approval follow the model anywhere on disk.