From 88dfc813e382e9de8bda5e06bd4f5b4686dd887e Mon Sep 17 00:00:00 2001 From: kevin zhao Date: Fri, 21 Nov 2025 14:23:26 -0500 Subject: [PATCH 1/6] bypass sandbox for policy approved commands --- codex-rs/core/src/exec_policy.rs | 8 ++++++-- codex-rs/core/src/tools/orchestrator.rs | 4 ++-- codex-rs/core/src/tools/runtimes/shell.rs | 8 +++++++- codex-rs/core/src/tools/runtimes/unified_exec.rs | 8 +++++++- codex-rs/core/src/tools/sandboxing.rs | 16 ++++++++++------ 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/codex-rs/core/src/exec_policy.rs b/codex-rs/core/src/exec_policy.rs index 2a5d3904eb..15e591648d 100644 --- a/codex-rs/core/src/exec_policy.rs +++ b/codex-rs/core/src/exec_policy.rs @@ -107,7 +107,9 @@ fn evaluate_with_policy( }) } } - Decision::Allow => Some(ApprovalRequirement::Skip), + Decision::Allow => Some(ApprovalRequirement::Skip { + bypass_sandbox: true, + }), }, Evaluation::NoMatch { .. } => None, } @@ -132,7 +134,9 @@ pub(crate) fn create_approval_requirement_for_command( ) { ApprovalRequirement::NeedsApproval { reason: None } } else { - ApprovalRequirement::Skip + ApprovalRequirement::Skip { + bypass_sandbox: false, + } } } diff --git a/codex-rs/core/src/tools/orchestrator.rs b/codex-rs/core/src/tools/orchestrator.rs index 7e8e152f67..b03c265388 100644 --- a/codex-rs/core/src/tools/orchestrator.rs +++ b/codex-rs/core/src/tools/orchestrator.rs @@ -57,7 +57,7 @@ impl ToolOrchestrator { default_approval_requirement(approval_policy, &turn_ctx.sandbox_policy) }); match requirement { - ApprovalRequirement::Skip => { + ApprovalRequirement::Skip { .. } => { otel.tool_decision(otel_tn, otel_ci, ReviewDecision::Approved, otel_cfg); } ApprovalRequirement::Forbidden { reason } => { @@ -103,7 +103,7 @@ impl ToolOrchestrator { let mut initial_sandbox = self .sandbox .select_initial(&turn_ctx.sandbox_policy, tool.sandbox_preference()); - if tool.wants_escalated_first_attempt(req) { + if tool.should_bypass_sandbox_first_attempt(req) { initial_sandbox = crate::exec::SandboxType::None; } // Platform-specific flag gating is handled by SandboxManager::select_initial diff --git a/codex-rs/core/src/tools/runtimes/shell.rs b/codex-rs/core/src/tools/runtimes/shell.rs index b46f72b485..0a3f66057c 100644 --- a/codex-rs/core/src/tools/runtimes/shell.rs +++ b/codex-rs/core/src/tools/runtimes/shell.rs @@ -117,8 +117,14 @@ impl Approvable for ShellRuntime { Some(req.approval_requirement.clone()) } - fn wants_escalated_first_attempt(&self, req: &ShellRequest) -> bool { + fn should_bypass_sandbox_first_attempt(&self, req: &ShellRequest) -> bool { req.with_escalated_permissions.unwrap_or(false) + || matches!( + req.approval_requirement, + ApprovalRequirement::Skip { + bypass_sandbox: true + } + ) } } diff --git a/codex-rs/core/src/tools/runtimes/unified_exec.rs b/codex-rs/core/src/tools/runtimes/unified_exec.rs index 3f03622596..9a4c2c4669 100644 --- a/codex-rs/core/src/tools/runtimes/unified_exec.rs +++ b/codex-rs/core/src/tools/runtimes/unified_exec.rs @@ -135,8 +135,14 @@ impl Approvable for UnifiedExecRuntime<'_> { Some(req.approval_requirement.clone()) } - fn wants_escalated_first_attempt(&self, req: &UnifiedExecRequest) -> bool { + fn should_bypass_sandbox_first_attempt(&self, req: &UnifiedExecRequest) -> bool { req.with_escalated_permissions.unwrap_or(false) + || matches!( + req.approval_requirement, + ApprovalRequirement::Skip { + bypass_sandbox: true + } + ) } } diff --git a/codex-rs/core/src/tools/sandboxing.rs b/codex-rs/core/src/tools/sandboxing.rs index f9e3e20eab..98589a99b2 100644 --- a/codex-rs/core/src/tools/sandboxing.rs +++ b/codex-rs/core/src/tools/sandboxing.rs @@ -89,8 +89,10 @@ pub(crate) struct ApprovalCtx<'a> { // Specifies what tool orchestrator should do with a given tool call. #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum ApprovalRequirement { - /// No approval required for this tool call - Skip, + /// No approval required for this tool call. `bypass_sandbox` signals that + /// the first attempt should skip sandboxing (e.g., when explicitly + /// greenlit by policy). + Skip { bypass_sandbox: bool }, /// Approval required for this tool call NeedsApproval { reason: Option }, /// Execution forbidden for this tool call @@ -113,7 +115,9 @@ pub(crate) fn default_approval_requirement( if needs_approval { ApprovalRequirement::NeedsApproval { reason: None } } else { - ApprovalRequirement::Skip + ApprovalRequirement::Skip { + bypass_sandbox: false, + } } } @@ -123,9 +127,9 @@ pub(crate) trait Approvable { fn approval_key(&self, req: &Req) -> Self::ApprovalKey; /// Some tools may request to skip the sandbox on the first attempt - /// (e.g., when the request explicitly asks for escalated permissions). - /// Defaults to `false`. - fn wants_escalated_first_attempt(&self, _req: &Req) -> bool { + /// (e.g., when a policy greenlights a command or the request explicitly + /// asks for escalated permissions). Defaults to `false`. + fn should_bypass_sandbox_first_attempt(&self, _req: &Req) -> bool { false } From fd73f37f652bc79c4aa5e3683a6314cd9c468e80 Mon Sep 17 00:00:00 2001 From: kevin zhao Date: Fri, 21 Nov 2025 14:26:53 -0500 Subject: [PATCH 2/6] better doc placement --- codex-rs/core/src/tools/sandboxing.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/codex-rs/core/src/tools/sandboxing.rs b/codex-rs/core/src/tools/sandboxing.rs index 98589a99b2..6830e8a86c 100644 --- a/codex-rs/core/src/tools/sandboxing.rs +++ b/codex-rs/core/src/tools/sandboxing.rs @@ -89,10 +89,12 @@ pub(crate) struct ApprovalCtx<'a> { // Specifies what tool orchestrator should do with a given tool call. #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum ApprovalRequirement { - /// No approval required for this tool call. `bypass_sandbox` signals that - /// the first attempt should skip sandboxing (e.g., when explicitly - /// greenlit by policy). - Skip { bypass_sandbox: bool }, + /// No approval required for this tool call. + Skip { + /// The first attempt should skip sandboxing (e.g., when explicitly + /// greenlit by policy). + bypass_sandbox: bool, + }, /// Approval required for this tool call NeedsApproval { reason: Option }, /// Execution forbidden for this tool call From 0f953e99e664a7068a858da8a31f55267d0b44bf Mon Sep 17 00:00:00 2001 From: kevin zhao Date: Fri, 21 Nov 2025 14:27:49 -0500 Subject: [PATCH 3/6] restore comment --- codex-rs/core/src/tools/sandboxing.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codex-rs/core/src/tools/sandboxing.rs b/codex-rs/core/src/tools/sandboxing.rs index 6830e8a86c..eea614a514 100644 --- a/codex-rs/core/src/tools/sandboxing.rs +++ b/codex-rs/core/src/tools/sandboxing.rs @@ -129,8 +129,8 @@ pub(crate) trait Approvable { fn approval_key(&self, req: &Req) -> Self::ApprovalKey; /// Some tools may request to skip the sandbox on the first attempt - /// (e.g., when a policy greenlights a command or the request explicitly - /// asks for escalated permissions). Defaults to `false`. + /// (e.g., when the request explicitly asks for escalated permissions). + /// Defaults to `false`. fn should_bypass_sandbox_first_attempt(&self, _req: &Req) -> bool { false } From 9eb6aeb4b90e999ff954cf432fdf2220990f4a1f Mon Sep 17 00:00:00 2001 From: kevin zhao Date: Fri, 21 Nov 2025 14:33:53 -0500 Subject: [PATCH 4/6] initial sandbox override enum --- codex-rs/core/src/tools/orchestrator.rs | 15 +++++++++------ codex-rs/core/src/tools/runtimes/shell.rs | 10 ++++++++-- codex-rs/core/src/tools/runtimes/unified_exec.rs | 10 ++++++++-- codex-rs/core/src/tools/sandboxing.rs | 12 +++++++++--- 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/codex-rs/core/src/tools/orchestrator.rs b/codex-rs/core/src/tools/orchestrator.rs index b03c265388..7b54da8f8c 100644 --- a/codex-rs/core/src/tools/orchestrator.rs +++ b/codex-rs/core/src/tools/orchestrator.rs @@ -100,12 +100,15 @@ impl ToolOrchestrator { } // 2) First attempt under the selected sandbox. - let mut initial_sandbox = self - .sandbox - .select_initial(&turn_ctx.sandbox_policy, tool.sandbox_preference()); - if tool.should_bypass_sandbox_first_attempt(req) { - initial_sandbox = crate::exec::SandboxType::None; - } + let initial_sandbox = match tool.sandbox_override(req) { + crate::tools::sandboxing::SandboxOverride::BypassSandboxFirstAttempt => { + crate::exec::SandboxType::None + } + crate::tools::sandboxing::SandboxOverride::NoOverride => self + .sandbox + .select_initial(&turn_ctx.sandbox_policy, tool.sandbox_preference()), + }; + // Platform-specific flag gating is handled by SandboxManager::select_initial // via crate::safety::get_platform_sandbox(). let initial_attempt = SandboxAttempt { diff --git a/codex-rs/core/src/tools/runtimes/shell.rs b/codex-rs/core/src/tools/runtimes/shell.rs index 0a3f66057c..85bde4b12e 100644 --- a/codex-rs/core/src/tools/runtimes/shell.rs +++ b/codex-rs/core/src/tools/runtimes/shell.rs @@ -12,6 +12,7 @@ use crate::tools::sandboxing::ApprovalCtx; use crate::tools::sandboxing::ApprovalRequirement; use crate::tools::sandboxing::ProvidesSandboxRetryData; use crate::tools::sandboxing::SandboxAttempt; +use crate::tools::sandboxing::SandboxOverride; use crate::tools::sandboxing::SandboxRetryData; use crate::tools::sandboxing::Sandboxable; use crate::tools::sandboxing::SandboxablePreference; @@ -117,14 +118,19 @@ impl Approvable for ShellRuntime { Some(req.approval_requirement.clone()) } - fn should_bypass_sandbox_first_attempt(&self, req: &ShellRequest) -> bool { - req.with_escalated_permissions.unwrap_or(false) + fn sandbox_override(&self, req: &ShellRequest) -> SandboxOverride { + if req.with_escalated_permissions.unwrap_or(false) || matches!( req.approval_requirement, ApprovalRequirement::Skip { bypass_sandbox: true } ) + { + SandboxOverride::BypassSandboxFirstAttempt + } else { + SandboxOverride::NoOverride + } } } diff --git a/codex-rs/core/src/tools/runtimes/unified_exec.rs b/codex-rs/core/src/tools/runtimes/unified_exec.rs index 9a4c2c4669..6c0a2466de 100644 --- a/codex-rs/core/src/tools/runtimes/unified_exec.rs +++ b/codex-rs/core/src/tools/runtimes/unified_exec.rs @@ -13,6 +13,7 @@ use crate::tools::sandboxing::ApprovalCtx; use crate::tools::sandboxing::ApprovalRequirement; use crate::tools::sandboxing::ProvidesSandboxRetryData; use crate::tools::sandboxing::SandboxAttempt; +use crate::tools::sandboxing::SandboxOverride; use crate::tools::sandboxing::SandboxRetryData; use crate::tools::sandboxing::Sandboxable; use crate::tools::sandboxing::SandboxablePreference; @@ -135,14 +136,19 @@ impl Approvable for UnifiedExecRuntime<'_> { Some(req.approval_requirement.clone()) } - fn should_bypass_sandbox_first_attempt(&self, req: &UnifiedExecRequest) -> bool { - req.with_escalated_permissions.unwrap_or(false) + fn sandbox_override(&self, req: &UnifiedExecRequest) -> SandboxOverride { + if req.with_escalated_permissions.unwrap_or(false) || matches!( req.approval_requirement, ApprovalRequirement::Skip { bypass_sandbox: true } ) + { + SandboxOverride::BypassSandboxFirstAttempt + } else { + SandboxOverride::NoOverride + } } } diff --git a/codex-rs/core/src/tools/sandboxing.rs b/codex-rs/core/src/tools/sandboxing.rs index eea614a514..37d62a0b77 100644 --- a/codex-rs/core/src/tools/sandboxing.rs +++ b/codex-rs/core/src/tools/sandboxing.rs @@ -123,6 +123,12 @@ pub(crate) fn default_approval_requirement( } } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum SandboxOverride { + NoOverride, + BypassSandboxFirstAttempt, +} + pub(crate) trait Approvable { type ApprovalKey: Hash + Eq + Clone + Debug + Serialize; @@ -130,9 +136,9 @@ pub(crate) trait Approvable { /// Some tools may request to skip the sandbox on the first attempt /// (e.g., when the request explicitly asks for escalated permissions). - /// Defaults to `false`. - fn should_bypass_sandbox_first_attempt(&self, _req: &Req) -> bool { - false + /// Defaults to `NoOverride`. + fn sandbox_override(&self, _req: &Req) -> SandboxOverride { + SandboxOverride::NoOverride } fn should_bypass_approval(&self, policy: AskForApproval, already_approved: bool) -> bool { From 26474119eaaf57d2185a271b3aafc94455d67271 Mon Sep 17 00:00:00 2001 From: kevin zhao Date: Fri, 21 Nov 2025 17:24:54 -0500 Subject: [PATCH 5/6] sandbox_override -> sandbox_mode_for_first_attempt --- codex-rs/core/src/tools/runtimes/shell.rs | 2 +- codex-rs/core/src/tools/runtimes/unified_exec.rs | 2 +- codex-rs/core/src/tools/sandboxing.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/codex-rs/core/src/tools/runtimes/shell.rs b/codex-rs/core/src/tools/runtimes/shell.rs index 85bde4b12e..56c72a8278 100644 --- a/codex-rs/core/src/tools/runtimes/shell.rs +++ b/codex-rs/core/src/tools/runtimes/shell.rs @@ -118,7 +118,7 @@ impl Approvable for ShellRuntime { Some(req.approval_requirement.clone()) } - fn sandbox_override(&self, req: &ShellRequest) -> SandboxOverride { + fn sandbox_mode_for_first_attempt(&self, req: &ShellRequest) -> SandboxOverride { if req.with_escalated_permissions.unwrap_or(false) || matches!( req.approval_requirement, diff --git a/codex-rs/core/src/tools/runtimes/unified_exec.rs b/codex-rs/core/src/tools/runtimes/unified_exec.rs index 6c0a2466de..0f306e6ff2 100644 --- a/codex-rs/core/src/tools/runtimes/unified_exec.rs +++ b/codex-rs/core/src/tools/runtimes/unified_exec.rs @@ -136,7 +136,7 @@ impl Approvable for UnifiedExecRuntime<'_> { Some(req.approval_requirement.clone()) } - fn sandbox_override(&self, req: &UnifiedExecRequest) -> SandboxOverride { + fn sandbox_mode_for_first_attempt(&self, req: &UnifiedExecRequest) -> SandboxOverride { if req.with_escalated_permissions.unwrap_or(false) || matches!( req.approval_requirement, diff --git a/codex-rs/core/src/tools/sandboxing.rs b/codex-rs/core/src/tools/sandboxing.rs index 37d62a0b77..df10db952e 100644 --- a/codex-rs/core/src/tools/sandboxing.rs +++ b/codex-rs/core/src/tools/sandboxing.rs @@ -137,7 +137,7 @@ pub(crate) trait Approvable { /// Some tools may request to skip the sandbox on the first attempt /// (e.g., when the request explicitly asks for escalated permissions). /// Defaults to `NoOverride`. - fn sandbox_override(&self, _req: &Req) -> SandboxOverride { + fn sandbox_mode_for_first_attempt(&self, _req: &Req) -> SandboxOverride { SandboxOverride::NoOverride } From 8cb20d2dfae1f64f42516bb202203bb041c73236 Mon Sep 17 00:00:00 2001 From: kevin zhao Date: Fri, 21 Nov 2025 17:25:22 -0500 Subject: [PATCH 6/6] imports in orchestrator.rs --- codex-rs/core/src/tools/orchestrator.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/codex-rs/core/src/tools/orchestrator.rs b/codex-rs/core/src/tools/orchestrator.rs index 7b54da8f8c..de23d510bf 100644 --- a/codex-rs/core/src/tools/orchestrator.rs +++ b/codex-rs/core/src/tools/orchestrator.rs @@ -14,6 +14,7 @@ use crate::tools::sandboxing::ApprovalCtx; use crate::tools::sandboxing::ApprovalRequirement; use crate::tools::sandboxing::ProvidesSandboxRetryData; use crate::tools::sandboxing::SandboxAttempt; +use crate::tools::sandboxing::SandboxOverride; use crate::tools::sandboxing::ToolCtx; use crate::tools::sandboxing::ToolError; use crate::tools::sandboxing::ToolRuntime; @@ -100,11 +101,9 @@ impl ToolOrchestrator { } // 2) First attempt under the selected sandbox. - let initial_sandbox = match tool.sandbox_override(req) { - crate::tools::sandboxing::SandboxOverride::BypassSandboxFirstAttempt => { - crate::exec::SandboxType::None - } - crate::tools::sandboxing::SandboxOverride::NoOverride => self + let initial_sandbox = match tool.sandbox_mode_for_first_attempt(req) { + SandboxOverride::BypassSandboxFirstAttempt => crate::exec::SandboxType::None, + SandboxOverride::NoOverride => self .sandbox .select_initial(&turn_ctx.sandbox_policy, tool.sandbox_preference()), };