bypass sandbox for policy approved commands (#7110)

allowing cmds greenlit by execpolicy to bypass sandbox + minor refactor
for a world where we have execpolicy rules with specific sandbox
requirements
This commit is contained in:
zhao-oai
2025-11-21 18:03:23 -05:00
committed by GitHub
parent 67975ed33a
commit 87b211709e
5 changed files with 61 additions and 19 deletions

View File

@@ -107,7 +107,9 @@ fn evaluate_with_policy(
}) })
} }
} }
Decision::Allow => Some(ApprovalRequirement::Skip), Decision::Allow => Some(ApprovalRequirement::Skip {
bypass_sandbox: true,
}),
}, },
Evaluation::NoMatch { .. } => None, Evaluation::NoMatch { .. } => None,
} }
@@ -132,7 +134,9 @@ pub(crate) fn create_approval_requirement_for_command(
) { ) {
ApprovalRequirement::NeedsApproval { reason: None } ApprovalRequirement::NeedsApproval { reason: None }
} else { } else {
ApprovalRequirement::Skip ApprovalRequirement::Skip {
bypass_sandbox: false,
}
} }
} }

View File

@@ -14,6 +14,7 @@ use crate::tools::sandboxing::ApprovalCtx;
use crate::tools::sandboxing::ApprovalRequirement; use crate::tools::sandboxing::ApprovalRequirement;
use crate::tools::sandboxing::ProvidesSandboxRetryData; use crate::tools::sandboxing::ProvidesSandboxRetryData;
use crate::tools::sandboxing::SandboxAttempt; use crate::tools::sandboxing::SandboxAttempt;
use crate::tools::sandboxing::SandboxOverride;
use crate::tools::sandboxing::ToolCtx; use crate::tools::sandboxing::ToolCtx;
use crate::tools::sandboxing::ToolError; use crate::tools::sandboxing::ToolError;
use crate::tools::sandboxing::ToolRuntime; use crate::tools::sandboxing::ToolRuntime;
@@ -57,7 +58,7 @@ impl ToolOrchestrator {
default_approval_requirement(approval_policy, &turn_ctx.sandbox_policy) default_approval_requirement(approval_policy, &turn_ctx.sandbox_policy)
}); });
match requirement { match requirement {
ApprovalRequirement::Skip => { ApprovalRequirement::Skip { .. } => {
otel.tool_decision(otel_tn, otel_ci, ReviewDecision::Approved, otel_cfg); otel.tool_decision(otel_tn, otel_ci, ReviewDecision::Approved, otel_cfg);
} }
ApprovalRequirement::Forbidden { reason } => { ApprovalRequirement::Forbidden { reason } => {
@@ -100,12 +101,13 @@ impl ToolOrchestrator {
} }
// 2) First attempt under the selected sandbox. // 2) First attempt under the selected sandbox.
let mut initial_sandbox = self let initial_sandbox = match tool.sandbox_mode_for_first_attempt(req) {
.sandbox SandboxOverride::BypassSandboxFirstAttempt => crate::exec::SandboxType::None,
.select_initial(&turn_ctx.sandbox_policy, tool.sandbox_preference()); SandboxOverride::NoOverride => self
if tool.wants_escalated_first_attempt(req) { .sandbox
initial_sandbox = crate::exec::SandboxType::None; .select_initial(&turn_ctx.sandbox_policy, tool.sandbox_preference()),
} };
// Platform-specific flag gating is handled by SandboxManager::select_initial // Platform-specific flag gating is handled by SandboxManager::select_initial
// via crate::safety::get_platform_sandbox(). // via crate::safety::get_platform_sandbox().
let initial_attempt = SandboxAttempt { let initial_attempt = SandboxAttempt {

View File

@@ -12,6 +12,7 @@ use crate::tools::sandboxing::ApprovalCtx;
use crate::tools::sandboxing::ApprovalRequirement; use crate::tools::sandboxing::ApprovalRequirement;
use crate::tools::sandboxing::ProvidesSandboxRetryData; use crate::tools::sandboxing::ProvidesSandboxRetryData;
use crate::tools::sandboxing::SandboxAttempt; use crate::tools::sandboxing::SandboxAttempt;
use crate::tools::sandboxing::SandboxOverride;
use crate::tools::sandboxing::SandboxRetryData; use crate::tools::sandboxing::SandboxRetryData;
use crate::tools::sandboxing::Sandboxable; use crate::tools::sandboxing::Sandboxable;
use crate::tools::sandboxing::SandboxablePreference; use crate::tools::sandboxing::SandboxablePreference;
@@ -117,8 +118,19 @@ impl Approvable<ShellRequest> for ShellRuntime {
Some(req.approval_requirement.clone()) Some(req.approval_requirement.clone())
} }
fn wants_escalated_first_attempt(&self, req: &ShellRequest) -> bool { fn sandbox_mode_for_first_attempt(&self, req: &ShellRequest) -> SandboxOverride {
req.with_escalated_permissions.unwrap_or(false) if req.with_escalated_permissions.unwrap_or(false)
|| matches!(
req.approval_requirement,
ApprovalRequirement::Skip {
bypass_sandbox: true
}
)
{
SandboxOverride::BypassSandboxFirstAttempt
} else {
SandboxOverride::NoOverride
}
} }
} }

View File

@@ -13,6 +13,7 @@ use crate::tools::sandboxing::ApprovalCtx;
use crate::tools::sandboxing::ApprovalRequirement; use crate::tools::sandboxing::ApprovalRequirement;
use crate::tools::sandboxing::ProvidesSandboxRetryData; use crate::tools::sandboxing::ProvidesSandboxRetryData;
use crate::tools::sandboxing::SandboxAttempt; use crate::tools::sandboxing::SandboxAttempt;
use crate::tools::sandboxing::SandboxOverride;
use crate::tools::sandboxing::SandboxRetryData; use crate::tools::sandboxing::SandboxRetryData;
use crate::tools::sandboxing::Sandboxable; use crate::tools::sandboxing::Sandboxable;
use crate::tools::sandboxing::SandboxablePreference; use crate::tools::sandboxing::SandboxablePreference;
@@ -135,8 +136,19 @@ impl Approvable<UnifiedExecRequest> for UnifiedExecRuntime<'_> {
Some(req.approval_requirement.clone()) Some(req.approval_requirement.clone())
} }
fn wants_escalated_first_attempt(&self, req: &UnifiedExecRequest) -> bool { fn sandbox_mode_for_first_attempt(&self, req: &UnifiedExecRequest) -> SandboxOverride {
req.with_escalated_permissions.unwrap_or(false) if req.with_escalated_permissions.unwrap_or(false)
|| matches!(
req.approval_requirement,
ApprovalRequirement::Skip {
bypass_sandbox: true
}
)
{
SandboxOverride::BypassSandboxFirstAttempt
} else {
SandboxOverride::NoOverride
}
} }
} }

View File

@@ -89,8 +89,12 @@ pub(crate) struct ApprovalCtx<'a> {
// Specifies what tool orchestrator should do with a given tool call. // Specifies what tool orchestrator should do with a given tool call.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum ApprovalRequirement { pub(crate) enum ApprovalRequirement {
/// No approval required for this tool call /// No approval required for this tool call.
Skip, Skip {
/// The first attempt should skip sandboxing (e.g., when explicitly
/// greenlit by policy).
bypass_sandbox: bool,
},
/// Approval required for this tool call /// Approval required for this tool call
NeedsApproval { reason: Option<String> }, NeedsApproval { reason: Option<String> },
/// Execution forbidden for this tool call /// Execution forbidden for this tool call
@@ -113,10 +117,18 @@ pub(crate) fn default_approval_requirement(
if needs_approval { if needs_approval {
ApprovalRequirement::NeedsApproval { reason: None } ApprovalRequirement::NeedsApproval { reason: None }
} else { } else {
ApprovalRequirement::Skip ApprovalRequirement::Skip {
bypass_sandbox: false,
}
} }
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum SandboxOverride {
NoOverride,
BypassSandboxFirstAttempt,
}
pub(crate) trait Approvable<Req> { pub(crate) trait Approvable<Req> {
type ApprovalKey: Hash + Eq + Clone + Debug + Serialize; type ApprovalKey: Hash + Eq + Clone + Debug + Serialize;
@@ -124,9 +136,9 @@ pub(crate) trait Approvable<Req> {
/// Some tools may request to skip the sandbox on the first attempt /// Some tools may request to skip the sandbox on the first attempt
/// (e.g., when the request explicitly asks for escalated permissions). /// (e.g., when the request explicitly asks for escalated permissions).
/// Defaults to `false`. /// Defaults to `NoOverride`.
fn wants_escalated_first_attempt(&self, _req: &Req) -> bool { fn sandbox_mode_for_first_attempt(&self, _req: &Req) -> SandboxOverride {
false SandboxOverride::NoOverride
} }
fn should_bypass_approval(&self, policy: AskForApproval, already_approved: bool) -> bool { fn should_bypass_approval(&self, policy: AskForApproval, already_approved: bool) -> bool {