From 738ef9f3bb4a5f17892eda6e5b52e06f11f680ea Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sat, 7 Nov 2026 01:37:27 +0000 Subject: [PATCH] Omit sensitive process inputs from spawn trace logs (#60034) ## Why Command arguments and environment variables can contain credentials. Logging them when spawning a child process can expose those credentials in trace output. ## What changed Replace argument, `arg0`, and environment contents in `spawn_child_async` trace logs with argument and environment counts and a flag indicating whether `arg0` is overridden. Keep the program, working directory, network sandbox policy, and stdio policy in the trace. ## Testing Add a regression test that captures trace output and verifies that argument, `arg0`, and environment canaries are omitted while the program path remains visible. GitOrigin-RevId: c317e7dcc6fd8b4eafb6fcfe98dc8634807c813d --- codex-rs/core/src/spawn.rs | 14 +++++++++++++- codex-rs/core/src/spawn_tests.rs | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 codex-rs/core/src/spawn_tests.rs diff --git a/codex-rs/core/src/spawn.rs b/codex-rs/core/src/spawn.rs index 3ac3493f9b..260adc595d 100644 --- a/codex-rs/core/src/spawn.rs +++ b/codex-rs/core/src/spawn.rs @@ -63,8 +63,16 @@ pub(crate) async fn spawn_child_async(request: SpawnChildRequest<'_>) -> std::io env.retain(|name, _| !is_non_inheritable_env_var(name)); + // Both command arguments and environment variables can contain credentials. trace!( - "spawn_child_async: {program:?} {args:?} {arg0:?} {cwd:?} {network_sandbox_policy:?} {stdio_policy:?} {env:?}" + ?program, + ?cwd, + ?network_sandbox_policy, + ?stdio_policy, + arg_count = args.len(), + has_arg0_override = arg0.is_some(), + env_count = env.len(), + "spawn_child_async" ); let mut cmd = match stdio_policy { @@ -135,3 +143,7 @@ pub(crate) async fn spawn_child_async(request: SpawnChildRequest<'_>) -> std::io cmd.kill_on_drop(true).spawn() } + +#[cfg(test)] +#[path = "spawn_tests.rs"] +mod tests; diff --git a/codex-rs/core/src/spawn_tests.rs b/codex-rs/core/src/spawn_tests.rs new file mode 100644 index 0000000000..217902955d --- /dev/null +++ b/codex-rs/core/src/spawn_tests.rs @@ -0,0 +1,33 @@ +#![cfg(test)] + +use super::*; +use std::sync::Arc; + +#[tokio::test] +async fn spawn_trace_omits_sensitive_inputs() -> std::io::Result<()> { + let dir = tempfile::tempdir()?; + let capture = tempfile::NamedTempFile::new()?; + let subscriber = tracing_subscriber::fmt() + .with_max_level(tracing::Level::TRACE) + .with_writer(Arc::new(capture.reopen()?)) + .finish(); + let _guard = tracing::subscriber::set_default(subscriber); + + let result = spawn_child_async(SpawnChildRequest { + program: dir.path().join("missing-test-program"), + args: vec!["canary-argument".into()], + arg0: Some("canary-arg0"), + cwd: AbsolutePathBuf::from_absolute_path(dir.path())?, + network_sandbox_policy: NetworkSandboxPolicy::Enabled, + network: None, + stdio_policy: StdioPolicy::RedirectForShellTool, + env: HashMap::from([("SETTING".into(), "canary-env-value".into())]), + }) + .await; + assert!(result.is_err()); + + let logs = std::fs::read_to_string(capture.path())?; + assert!(logs.contains("missing-test-program")); + assert!(!logs.contains("canary-")); + Ok(()) +}