Files
codex/codex-rs/shell-escalation/src/unix/mod.rs
T
Michael Bolin b4cb989563 refactor: prepare unified exec for zsh-fork backend (#13392)
## Why

`shell_zsh_fork` already provides stronger guarantees around which
executables receive elevated permissions. To reuse that machinery from
unified exec without pushing Unix-specific escalation details through
generic runtime code, the escalation bootstrap and session lifetime
handling need a cleaner boundary.

That boundary also needs to be safe for long-lived sessions: when an
intercepted shell session is closed or pruned, any in-flight approval
workers and any already-approved escalated child they spawned must be
torn down with the session, and the inherited escalation socket must not
leak into unrelated subprocesses.

## What Changed

- Extracted a reusable `EscalationSession` and
`EscalateServer::start_session(...)` in `shell-escalation` so callers
can get the wrapper/socket env overlay and keep the escalation server
alive without immediately running a one-shot command.
- Documented that `EscalationSession::env()` and
`ShellCommandExecutor::run(...)` exchange only that env overlay, which
callers must merge into their own base shell environment.
- Clarified the prepared-exec helper boundary in `core` by naming the
new helper APIs around `ExecRequest`, while keeping the legacy
`execute_env(...)` entrypoints as thin compatibility wrappers for
existing callers that still use the older naming.
- Added a small post-spawn hook on the prepared execution path so the
parent copy of the inheritable escalation socket is closed immediately
after both the existing one-shot shell-command spawn and the
unified-exec spawn.
- Made session teardown explicit with session-scoped cancellation:
dropping an `EscalationSession` or canceling its parent request now
stops intercept workers, and the server-spawned escalated child uses
`kill_on_drop(true)` so teardown cannot orphan an already-approved
child.
- Added `UnifiedExecBackendConfig` plumbing through `ToolsConfig`, a
`shell::zsh_fork_backend` facade, and an opaque unified-exec
spawn-lifecycle hook so unified exec can prepare a wrapped `zsh -c/-lc`
request without storing `EscalationSession` directly in generic
process/runtime code.
- Kept the existing `shell_command` zsh-fork behavior intact on top of
the new bootstrap path. Tool selection is unchanged in this PR: when
`shell_zsh_fork` is enabled, `ShellCommand` still wins over
`exec_command`.

## Verification

- `cargo test -p codex-shell-escalation`
  - includes coverage for `start_session_exposes_wrapper_env_overlay`
  - includes coverage for `exec_closes_parent_socket_after_shell_spawn`
- includes coverage for
`dropping_session_aborts_intercept_workers_and_kills_spawned_child`
- `cargo test -p codex-core
shell_zsh_fork_prefers_shell_command_over_unified_exec`
- `cargo test -p codex-core --test all
shell_zsh_fork_prompts_for_skill_script_execution`


---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/13392).
* #13432
* __->__ #13392
2026-03-05 08:55:12 +00:00

79 lines
2.8 KiB
Rust

//! Unix shell-escalation protocol implementation.
//!
//! A patched shell invokes an exec wrapper on every `exec()` attempt. The wrapper sends an
//! `EscalateRequest` over the inherited `CODEX_ESCALATE_SOCKET`, and the server decides whether to
//! run the command directly (`Run`) or execute it on the server side (`Escalate`).
//!
//! Of key importance is the `EscalateRequest` includes a file descriptor for a socket
//! that the server can use to send the response to the execve wrapper. In this
//! way, all descendents of the Server process can use the file descriptor
//! specified by the `CODEX_ESCALATE_SOCKET` environment variable to _send_ escalation requests,
//! but responses are read from a separate socket that is created for each request, which
//! allows the server to handle multiple concurrent escalation requests.
//!
//! ### Escalation flow
//!
//! Command Server Shell Execve Wrapper
//! |
//! o----->o
//! | |
//! | o--(exec)-->o
//! | | |
//! |o<-(EscalateReq)--o
//! || | |
//! |o--(Escalate)---->o
//! || | |
//! |o<---------(fds)--o
//! || | |
//! o<------o | |
//! | || | |
//! x------>o | |
//! || | |
//! |x--(exit code)--->o
//! | | |
//! | o<--(exit)--x
//! | |
//! o<-----x
//!
//! ### Non-escalation flow
//!
//! Server Shell Execve Wrapper Command
//! |
//! o----->o
//! | |
//! | o--(exec)-->o
//! | | |
//! |o<-(EscalateReq)--o
//! || | |
//! |o-(Run)---------->o
//! | | |
//! | | x--(exec)-->o
//! | | |
//! | o<--------------(exit)--x
//! | |
//! o<-----x
//!
pub mod escalate_client;
pub mod escalate_protocol;
pub mod escalate_server;
pub mod escalation_policy;
pub mod execve_wrapper;
pub mod socket;
pub mod stopwatch;
pub use self::escalate_client::run_shell_escalation_execve_wrapper;
pub use self::escalate_protocol::EscalateAction;
pub use self::escalate_protocol::EscalationDecision;
pub use self::escalate_protocol::EscalationExecution;
pub use self::escalate_server::EscalateServer;
pub use self::escalate_server::EscalationSession;
pub use self::escalate_server::ExecParams;
pub use self::escalate_server::ExecResult;
pub use self::escalate_server::PreparedExec;
pub use self::escalate_server::ShellCommandExecutor;
pub use self::escalation_policy::EscalationPolicy;
pub use self::execve_wrapper::main_execve_wrapper;
pub use self::stopwatch::Stopwatch;
pub use codex_protocol::approvals::EscalationPermissions;
pub use codex_protocol::approvals::Permissions;