From d747d1d492db3bc2b9a24a431d4bb9aea2f7be69 Mon Sep 17 00:00:00 2001 From: kevin zhao Date: Fri, 21 Nov 2025 15:26:22 -0500 Subject: [PATCH] do not send allow_prefix if execpolicy is disabled --- codex-rs/core/src/codex.rs | 9 ++++ codex-rs/core/src/exec_policy.rs | 42 +++++++++++++++++-- codex-rs/core/src/tools/handlers/shell.rs | 2 + .../core/src/unified_exec/session_manager.rs | 3 ++ 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 1352475c81..c3653d12fb 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -1116,6 +1116,15 @@ impl Session { .enabled(feature) } + pub(crate) async fn features(&self) -> Features { + self.state + .lock() + .await + .session_configuration + .features + .clone() + } + async fn send_raw_response_items(&self, turn_context: &TurnContext, items: &[ResponseItem]) { for item in items { self.send_event( diff --git a/codex-rs/core/src/exec_policy.rs b/codex-rs/core/src/exec_policy.rs index 10ee2b4154..482a395745 100644 --- a/codex-rs/core/src/exec_policy.rs +++ b/codex-rs/core/src/exec_policy.rs @@ -157,12 +157,13 @@ fn requirement_from_decision( /// Return an allow-prefix option when a single plain command needs approval without /// any matching policy rule. We only surface the prefix opt-in when execpolicy did /// not already drive the decision (NoMatch) and when the command is a single -/// unrolled command (multi-part scripts shouldn’t be whitelisted via prefix). +/// unrolled command (multi-part scripts shouldn’t be whitelisted via prefix) and +/// when execpolicy feature is enabled. fn allow_prefix_if_applicable( commands: &[Vec], - evaluation: &Evaluation, + features: &Features, ) -> Option> { - if matches!(evaluation, Evaluation::NoMatch) && commands.len() == 1 { + if features.enabled(Feature::ExecPolicy) && commands.len() == 1 { return Some(commands[0].clone()); } @@ -173,6 +174,7 @@ pub(crate) fn create_approval_requirement_for_command( policy: &Policy, command: &[String], approval_policy: AskForApproval, + features: &Features, sandbox_policy: &SandboxPolicy, sandbox_permissions: SandboxPermissions, ) -> ApprovalRequirement { @@ -190,7 +192,7 @@ pub(crate) fn create_approval_requirement_for_command( ) { ApprovalRequirement::NeedsApproval { reason: None, - allow_prefix: allow_prefix_if_applicable(&commands, &evaluation), + allow_prefix: allow_prefix_if_applicable(&commands, features), } } else { ApprovalRequirement::Skip { @@ -350,6 +352,7 @@ prefix_rule(pattern=["rm"], decision="forbidden") &policy, &forbidden_script, AskForApproval::OnRequest, + &Features::with_defaults(), &SandboxPolicy::DangerFullAccess, SandboxPermissions::UseDefault, ); @@ -376,6 +379,7 @@ prefix_rule(pattern=["rm"], decision="forbidden") &policy, &command, AskForApproval::OnRequest, + &Features::with_defaults(), &SandboxPolicy::DangerFullAccess, SandboxPermissions::UseDefault, ); @@ -403,6 +407,7 @@ prefix_rule(pattern=["rm"], decision="forbidden") &policy, &command, AskForApproval::Never, + &Features::with_defaults(), &SandboxPolicy::DangerFullAccess, SandboxPermissions::UseDefault, ); @@ -424,6 +429,7 @@ prefix_rule(pattern=["rm"], decision="forbidden") &empty_policy, &command, AskForApproval::UnlessTrusted, + &Features::with_defaults(), &SandboxPolicy::ReadOnly, SandboxPermissions::UseDefault, ); @@ -494,6 +500,7 @@ prefix_rule(pattern=["rm"], decision="forbidden") &empty_policy, &command, AskForApproval::UnlessTrusted, + &Features::with_defaults(), &SandboxPolicy::ReadOnly, SandboxPermissions::UseDefault, ); @@ -507,6 +514,31 @@ prefix_rule(pattern=["rm"], decision="forbidden") ); } + #[test] + fn allow_prefix_is_disabled_when_execpolicy_feature_disabled() { + let command = vec!["python".to_string()]; + + let mut features = Features::with_defaults(); + features.disable(Feature::ExecPolicy); + + let requirement = create_approval_requirement_for_command( + &Policy::empty(), + &command, + AskForApproval::UnlessTrusted, + &features, + &SandboxPolicy::ReadOnly, + SandboxPermissions::UseDefault, + ); + + assert_eq!( + requirement, + ApprovalRequirement::NeedsApproval { + reason: None, + allow_prefix: None, + } + ); + } + #[test] fn allow_prefix_is_omitted_when_policy_prompts() { let policy_src = r#"prefix_rule(pattern=["rm"], decision="prompt")"#; @@ -521,6 +553,7 @@ prefix_rule(pattern=["rm"], decision="forbidden") &policy, &command, AskForApproval::OnRequest, + &Features::with_defaults(), &SandboxPolicy::DangerFullAccess, SandboxPermissions::UseDefault, ); @@ -545,6 +578,7 @@ prefix_rule(pattern=["rm"], decision="forbidden") &Policy::empty(), &command, AskForApproval::UnlessTrusted, + &Features::with_defaults(), &SandboxPolicy::ReadOnly, SandboxPermissions::UseDefault, ); diff --git a/codex-rs/core/src/tools/handlers/shell.rs b/codex-rs/core/src/tools/handlers/shell.rs index 72e2dcdd54..78765addb2 100644 --- a/codex-rs/core/src/tools/handlers/shell.rs +++ b/codex-rs/core/src/tools/handlers/shell.rs @@ -231,6 +231,7 @@ impl ShellHandler { let event_ctx = ToolEventCtx::new(session.as_ref(), turn.as_ref(), &call_id, None); emitter.begin(event_ctx).await; + let features = session.features().await; let exec_policy = session.current_exec_policy().await; let exec_policy = exec_policy.read().await; let req = ShellRequest { @@ -244,6 +245,7 @@ impl ShellHandler { &exec_policy, &exec_params.command, turn.approval_policy, + &features, &turn.sandbox_policy, SandboxPermissions::from(exec_params.with_escalated_permissions.unwrap_or(false)), ), diff --git a/codex-rs/core/src/unified_exec/session_manager.rs b/codex-rs/core/src/unified_exec/session_manager.rs index 16ecbea2fe..01d04cdacf 100644 --- a/codex-rs/core/src/unified_exec/session_manager.rs +++ b/codex-rs/core/src/unified_exec/session_manager.rs @@ -552,6 +552,8 @@ impl UnifiedExecSessionManager { context: &UnifiedExecContext, ) -> Result { let env = apply_unified_exec_env(create_env(&context.turn.shell_environment_policy)); + let features = context.session.features().await; + let features = context.session.features().await; let mut orchestrator = ToolOrchestrator::new(); let mut runtime = UnifiedExecRuntime::new(self); let exec_policy = context.session.current_exec_policy().await; @@ -566,6 +568,7 @@ impl UnifiedExecSessionManager { &exec_policy, command, context.turn.approval_policy, + &features, &context.turn.sandbox_policy, SandboxPermissions::from(with_escalated_permissions.unwrap_or(false)), ),