Prevent launch context from reaching child processes (#37607)

## Why

Model-reachable child processes should not inherit Codex launch context.

## What changed

- Treat `OPENAI_FEDERATION_RULE_ID` and `OPENAI_IDENTITY_TOKEN_FILE` as non-inheritable environment variables, with case-insensitive matching.
- Remove them after shell environment policy overrides and before spawning commands across execution, MCP, hooks, Git helpers, and remote helper processes.

## Testing

- Cover inherited and explicitly configured variants, including mixed-case names.
- Verify the variables are absent from real child environments and app-server command and process execution.

GitOrigin-RevId: 2535527893985fef0995617f4c5b2462bea7c136
This commit is contained in:
cooper-oai
2026-08-08 16:58:26 +00:00
committed by copyberry
parent 3aae5d885b
commit c4513cb982
25 changed files with 269 additions and 36 deletions
+3
View File
@@ -8,6 +8,7 @@ use tokio::process::Command;
use tracing::trace;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::shell_environment::is_non_inheritable_env_var;
/// Experimental environment variable that will be set to some non-empty value
/// if both of the following are true:
@@ -60,6 +61,8 @@ pub(crate) async fn spawn_child_async(request: SpawnChildRequest<'_>) -> std::io
mut env,
} = request;
env.retain(|name, _| !is_non_inheritable_env_var(name));
trace!(
"spawn_child_async: {program:?} {args:?} {arg0:?} {cwd:?} {network_sandbox_policy:?} {stdio_policy:?} {env:?}"
);
+2
View File
@@ -23,6 +23,7 @@ pub(crate) use codex_network_proxy::is_managed_proxy_env_var;
pub(crate) use codex_network_proxy::strip_managed_proxy_env;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::shell_environment::is_non_inheritable_env_var;
use codex_sandboxing::SandboxCommand;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
@@ -304,6 +305,7 @@ fn build_override_exports(
.keys()
.map(String::as_str)
.chain(restore_even_when_absent.iter().copied())
.filter(|key| !is_non_inheritable_env_var(key))
.filter(|key| is_valid_shell_variable_name(key))
.collect::<Vec<_>>();
keys.sort_unstable();
+11 -4
View File
@@ -1082,10 +1082,16 @@ fn maybe_wrap_shell_lc_with_snapshot_does_not_embed_override_values_in_argv() {
"-lc".to_string(),
"printf '%s' \"$OPENAI_API_KEY\"".to_string(),
];
let explicit_env_overrides = HashMap::from([(
"OPENAI_API_KEY".to_string(),
"super-secret-value".to_string(),
)]);
let explicit_env_overrides = HashMap::from([
(
"OPENAI_API_KEY".to_string(),
"super-secret-value".to_string(),
),
(
"openai_identity_token_file".to_string(),
"/run/identity-token".to_string(),
),
]);
let rewritten = maybe_wrap_shell_lc_with_snapshot(
&command,
&session_shell,
@@ -1099,6 +1105,7 @@ fn maybe_wrap_shell_lc_with_snapshot_does_not_embed_override_values_in_argv() {
);
assert!(!rewritten[2].contains("super-secret-value"));
assert!(!rewritten[2].contains("openai_identity_token_file"));
let output = Command::new(&rewritten[0])
.args(&rewritten[1..])
.env("OPENAI_API_KEY", "super-secret-value")
@@ -66,6 +66,7 @@ use codex_protocol::error::SandboxErr;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::ExecCommandSource;
use codex_protocol::protocol::TerminalInteractionEvent;
use codex_protocol::shell_environment::is_non_inheritable_env_var;
use codex_sandboxing::SandboxCommand;
use codex_tools::ToolName;
use codex_utils_output_truncation::approx_tokens_from_byte_count;
@@ -123,7 +124,10 @@ fn exec_env_policy_from_shell_policy(
.collect::<Vec<_>>();
exclude.push(CODEX_PERMISSION_PROFILE_ENV_VAR.to_string());
let mut r#set = policy.r#set.clone();
r#set.retain(|key, _| !key.eq_ignore_ascii_case(CODEX_PERMISSION_PROFILE_ENV_VAR));
r#set.retain(|key, _| {
!key.eq_ignore_ascii_case(CODEX_PERMISSION_PROFILE_ENV_VAR)
&& !is_non_inheritable_env_var(key)
});
codex_exec_server::ExecEnvPolicy {
inherit: policy.inherit.clone(),
ignore_default_excludes: policy.ignore_default_excludes,
@@ -144,8 +148,9 @@ fn env_overlay_for_exec_server(
request_env
.iter()
.filter(|(key, value)| {
key.as_str() == CODEX_PERMISSION_PROFILE_ENV_VAR
|| local_policy_env.get(*key) != Some(*value)
!is_non_inheritable_env_var(key)
&& (key.as_str() == CODEX_PERMISSION_PROFILE_ENV_VAR
|| local_policy_env.get(*key) != Some(*value))
})
.map(|(key, value)| (key.clone(), value.clone()))
.collect()
@@ -51,6 +51,7 @@ fn env_overlay_for_exec_server_keeps_runtime_changes_only() {
let request_env = HashMap::from([
("HOME".to_string(), "/client-home".to_string()),
("PATH".to_string(), "/sandbox-path".to_string()),
("OpenAI_Federation_Rule_Id".to_string(), "rule".to_string()),
("SHELL_SET".to_string(), "policy".to_string()),
("CODEX_THREAD_ID".to_string(), "thread-1".to_string()),
(
@@ -81,13 +82,17 @@ fn env_overlay_for_exec_server_keeps_runtime_changes_only() {
}
#[test]
fn exec_env_policy_excludes_runtime_permission_profile() {
fn exec_env_policy_excludes_non_inheritable_and_runtime_variables() {
let policy = ShellEnvironmentPolicy {
r#set: HashMap::from([
(
"codex_permission_profile".to_string(),
"stale-profile".to_string(),
),
(
"openai_identity_token_file".to_string(),
"/run/identity-token".to_string(),
),
("KEEP".to_string(), "value".to_string()),
]),
..Default::default()