Building on the work of https://github.com/openai/codex/pull/1702, this changes how a shell call to `apply_patch` is handled. Previously, a shell call to `apply_patch` was always handled in-process, never leveraging a sandbox. To determine whether the `apply_patch` operation could be auto-approved, the `is_write_patch_constrained_to_writable_paths()` function would check if all the paths listed in the paths were writable. If so, the agent would apply the changes listed in the patch. Unfortunately, this approach afforded a loophole: symlinks! * For a soft link, we could fix this issue by tracing the link and checking whether the target is in the set of writable paths, however... * ...For a hard link, things are not as simple. We can run `stat FILE` to see if the number of links is greater than 1, but then we would have to do something potentially expensive like `find . -inum <inode_number>` to find the other paths for `FILE`. Further, even if this worked, this approach runs the risk of a [TOCTOU](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use) race condition, so it is not robust. The solution, implemented in this PR, is to take the virtual execution of the `apply_patch` CLI into an _actual_ execution using `codex --codex-run-as-apply-patch PATCH`, which we can run under the sandbox the user specified, just like any other `shell` call. This, of course, assumes that the sandbox prevents writing through symlinks as a mechanism to write to folders that are not in the writable set configured by the sandbox. I verified this by testing the following on both Mac and Linux: ```shell #!/usr/bin/env bash set -euo pipefail # Can running a command in SANDBOX_DIR write a file in EXPLOIT_DIR? # Codex is run in SANDBOX_DIR, so writes should be constrianed to this directory. SANDBOX_DIR=$(mktemp -d -p "$HOME" sandboxtesttemp.XXXXXX) # EXPLOIT_DIR is outside of SANDBOX_DIR, so let's see if we can write to it. EXPLOIT_DIR=$(mktemp -d -p "$HOME" sandboxtesttemp.XXXXXX) echo "SANDBOX_DIR: $SANDBOX_DIR" echo "EXPLOIT_DIR: $EXPLOIT_DIR" cleanup() { # Only remove if it looks sane and still exists [[ -n "${SANDBOX_DIR:-}" && -d "$SANDBOX_DIR" ]] && rm -rf -- "$SANDBOX_DIR" [[ -n "${EXPLOIT_DIR:-}" && -d "$EXPLOIT_DIR" ]] && rm -rf -- "$EXPLOIT_DIR" } trap cleanup EXIT echo "I am the original content" > "${EXPLOIT_DIR}/original.txt" # Drop the -s to test hard links. ln -s "${EXPLOIT_DIR}/original.txt" "${SANDBOX_DIR}/link-to-original.txt" cat "${SANDBOX_DIR}/link-to-original.txt" if [[ "$(uname)" == "Linux" ]]; then SANDBOX_SUBCOMMAND=landlock else SANDBOX_SUBCOMMAND=seatbelt fi # Attempt the exploit cd "${SANDBOX_DIR}" codex debug "${SANDBOX_SUBCOMMAND}" bash -lc "echo pwned > ./link-to-original.txt" || true cat "${EXPLOIT_DIR}/original.txt" ``` Admittedly, this change merits a proper integration test, but I think I will have to do that in a follow-up PR.
48 lines
1.1 KiB
Rust
48 lines
1.1 KiB
Rust
//! Root of the `codex-core` library.
|
|
|
|
// Prevent accidental direct writes to stdout/stderr in library code. All
|
|
// user-visible output must go through the appropriate abstraction (e.g.,
|
|
// the TUI or the tracing stack).
|
|
#![deny(clippy::print_stdout, clippy::print_stderr)]
|
|
|
|
mod apply_patch;
|
|
mod bash;
|
|
mod chat_completions;
|
|
mod client;
|
|
mod client_common;
|
|
pub mod codex;
|
|
pub use codex::Codex;
|
|
pub use codex::CodexSpawnOk;
|
|
pub mod codex_wrapper;
|
|
pub mod config;
|
|
pub mod config_profile;
|
|
pub mod config_types;
|
|
mod conversation_history;
|
|
pub mod error;
|
|
pub mod exec;
|
|
pub mod exec_env;
|
|
mod flags;
|
|
pub mod git_info;
|
|
mod is_safe_command;
|
|
mod mcp_connection_manager;
|
|
mod mcp_tool_call;
|
|
mod message_history;
|
|
mod model_provider_info;
|
|
pub use model_provider_info::ModelProviderInfo;
|
|
pub use model_provider_info::WireApi;
|
|
pub use model_provider_info::built_in_model_providers;
|
|
mod models;
|
|
mod openai_model_info;
|
|
mod openai_tools;
|
|
pub mod plan_tool;
|
|
mod project_doc;
|
|
pub mod protocol;
|
|
mod rollout;
|
|
mod safety;
|
|
pub mod shell;
|
|
mod user_notification;
|
|
pub mod util;
|
|
|
|
pub use apply_patch::CODEX_APPLY_PATCH_ARG1;
|
|
pub use client_common::model_supports_reasoning_summaries;
|