mirror of
https://github.com/openai/codex.git
synced 2026-11-06 20:20:34 +00:00
Bind final-message output before running sandboxed tools (#58974)
## Why `codex exec --output-last-message` wrote through a pathname after tools finished, allowing sandbox activity to redirect the final write by replacing that path with a symlink. The related risk is described in https://github.com/openai/codex/pull/25956. ## What changed - Open the destination before starting the agent and retain its handle until the final write. - Reject symlinks, Windows reparse points, and multiply linked files; verify that the pathname still identifies the bound file before truncating and after writing. - Propagate output errors so the command fails when the destination cannot be opened or written safely. - Preserve inherited Unix output streams, FIFOs, devices, and Windows `NUL` support, while avoiding duplicate final answers when human output targets stdout. ## Testing Add regression coverage for sandbox-created symlinks across consecutive invocations, redirected stdout, delayed truncation, hard links, replaced paths, and Windows stream binding and filesystem identity checks. GitOrigin-RevId: 4a31bd28a5eb2d0a9e200cb658eb7d19dc93e933
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
use anyhow::Context;
|
||||
use std::path::Path;
|
||||
|
||||
use codex_app_server_protocol::ServerNotification;
|
||||
use codex_core::config::Config;
|
||||
use codex_exec_server::BoundOutput;
|
||||
use codex_protocol::protocol::SessionConfiguredEvent;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -25,24 +27,21 @@ pub(crate) trait EventProcessor {
|
||||
/// Handle a local exec warning that is not represented as an app-server notification.
|
||||
fn process_warning(&mut self, message: String) -> CodexStatus;
|
||||
|
||||
fn print_final_output(&mut self) {}
|
||||
fn print_final_output(&mut self, output: Option<BoundOutput>) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
pub(crate) fn handle_last_message(last_agent_message: Option<&str>, output_file: &Path) {
|
||||
let message = last_agent_message.unwrap_or_default();
|
||||
write_last_message_file(message, Some(output_file));
|
||||
pub(crate) fn handle_last_message(
|
||||
last_agent_message: Option<&str>,
|
||||
path: &Path,
|
||||
file: BoundOutput,
|
||||
) -> anyhow::Result<()> {
|
||||
file.write(last_agent_message.unwrap_or_default().as_bytes())
|
||||
.with_context(|| format!("Failed to write last message file {path:?}"))?;
|
||||
if last_agent_message.is_none() {
|
||||
eprintln!(
|
||||
"Warning: no last agent message; wrote empty content to {}",
|
||||
output_file.display()
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_last_message_file(contents: &str, last_message_path: Option<&Path>) {
|
||||
if let Some(path) = last_message_path
|
||||
&& let Err(e) = std::fs::write(path, contents)
|
||||
{
|
||||
eprintln!("Failed to write last message file {path:?}: {e}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user