[RIP-SEC] Command safe-list keys on the executable basename, so `./cat` (an attacker-controlled file) is auto-approved without a prompt under `UnlessTrusted`

Open 💬 0 comments Opened Aug 5, 2026 by scadastrangelove

Summary

The "known-safe" command list — which, under AskForApproval::UnlessTrusted, lets a command run
without prompting the user — is matched against the executable's basename only. So any path
spelling of a name on the list (./cat, /tmp/evil/cat, a PATH-shadowed cat) classifies as the
trusted cat and is auto-approved, even though a different file actually executes.

Where

  • codex-rs/shell-command/src/command_safety/is_dangerous_command.rsexecutable_name_lookup_key

returns Path::new(raw).file_name() (basename only; on Windows it also lowercases and strips
.exe/.cmd/.bat/.com).

  • codex-rs/core/src/exec_policy.rsrender_decision_for_unmatched_command returns

Decision::Allow (no prompt) when is_known_safe && !used_complex_parsing && approval_policy ==
UnlessTrusted
.

  • Present on current main.

Reproduction

is_known_safe_command returns true for ["./cat", ...], ["../cat", ...],
["/tmp/evil/cat", ...], ["a/b/cat", ...] — the same as bare ["cat", ...] — while ["./payload"]
returns false. So an attacker who stages ./cat (writable under workspace-write) and has it invoked
gets the silent auto-approve that an honestly-named ./payload would not.

Impact

Under UnlessTrusted, an attacker-controlled binary whose basename matches the safe-list bypasses the
approval prompt the user relies on. The command still runs inside the active sandbox — this is an
approval-desync, not a sandbox escape.

Suggested fix

Only honor the safe-list for a bare command name (no /), or resolve argv[0] against PATH/cwd and
confirm it maps to the expected system binary before treating it as known-safe; require a prompt for
any path-qualified argv[0].

Prior art / not a duplicate

Same family as the "GitPwned" allowlist finding and CVE-2025-54558 (ripgrep --pre/-z), but those
trust the ARGUMENTS of an allow-listed command; this is basename-vs-path on argv[0] (a distinct
mechanism — the GitPwned writeup states basename spoofing was not their vector).

---
Found with the rust-in-peace pipeline
(AI-assisted Rust vulnerability research).

View original on GitHub ↗