mirror of
https://github.com/openai/codex.git
synced 2026-10-21 17:48:44 +00:00
## Why Large shell snapshots need a replay path that does not add their state to the child process environment. ## What changed - Keep captured state cached in memory and give each Bash or Zsh launch an independent, read-only file descriptor to source. Unlink the file before writing shell state, create it under the sandbox-protected daemon directory, and close the carrier descriptor before restoring functions. - Probe descriptor-path access inside the capture sandbox. Retain environment replay for `sh` and when descriptor paths are unavailable; use normal shell startup if preparing the replay file fails. - Group options and aliases during capture so sourcing preserves their parsing behavior. ## Testing Add coverage for concurrent replays with 480 KiB payloads, pipe and TTY execution, stdin preservation, descriptor cleanup, sandbox protection, blocked descriptor-path fallback, and alias/option parsing equivalence. GitOrigin-RevId: 8103d5c37ce4d1885628a4efba3551549f12bea8
49 lines
2.0 KiB
Rust
49 lines
2.0 KiB
Rust
//! Per-launch snapshot transport, created under the sandbox-protected daemon root.
|
|
//! No named file ever contains shell state, and the cache owns bytes, not descriptors.
|
|
//! The caller keeps the reader alive through spawn.
|
|
|
|
use codex_shell_command::shell_detect::ShellType;
|
|
use std::fs::File;
|
|
use std::io;
|
|
use std::io::Write;
|
|
use std::os::fd::AsRawFd;
|
|
use std::os::unix::fs::MetadataExt;
|
|
|
|
pub(super) fn materialize(shell: ShellType, state: &str) -> io::Result<File> {
|
|
// Mode 0700 alone cannot stop a same-user sandboxed command from replacing
|
|
// the pathname or retaining a writable handle before unlink. Reuse the root
|
|
// that every filesystem-restricted sandbox hides, independent of CODEX_HOME.
|
|
let root = codex_uds::prepare_shared_daemon_socket_directory()?;
|
|
let directory = tempfile::tempdir_in(root)?;
|
|
let writer = tempfile::NamedTempFile::new_in(directory.path())?;
|
|
// Open independently, rather than dup: writing must not advance the reader.
|
|
let reader = File::open(writer.path())?;
|
|
let reader = File::from(rustix::io::fcntl_dupfd_cloexec(&reader, /*min*/ 10)?);
|
|
let (mut writer, path) = writer.into_parts();
|
|
path.close()?;
|
|
directory.close()?;
|
|
if reader.metadata()?.nlink() != 0 {
|
|
return Err(io::Error::other("shell snapshot transport is still linked"));
|
|
}
|
|
|
|
let fd = reader.as_raw_fd();
|
|
// Source has opened its own reader before this runs. Close the carrier before
|
|
// restoring functions that may shadow exec/unset, without borrowing stdin.
|
|
let close = match shell {
|
|
ShellType::Bash => format!("\\exec {fd}<&-\n"),
|
|
ShellType::Zsh => format!(
|
|
"__codex_snapshot_fd={fd}\nexec {{__codex_snapshot_fd}}<&-\nunset __codex_snapshot_fd\n"
|
|
),
|
|
ShellType::Sh | ShellType::PowerShell | ShellType::Cmd => {
|
|
return Err(io::Error::other("unsupported snapshot shell"));
|
|
}
|
|
};
|
|
writer.write_all(close.as_bytes())?;
|
|
writer.write_all(state.as_bytes())?;
|
|
Ok(reader)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "shell_snapshot_file_tests.rs"]
|
|
mod tests;
|