@@ -456,8 +456,6 @@ impl Session {
456456 client,
457457 tools_config : ToolsConfig :: new ( & ToolsConfigParams {
458458 model_family : & config. model_family ,
459- approval_policy,
460- sandbox_policy : sandbox_policy. clone ( ) ,
461459 include_plan_tool : config. include_plan_tool ,
462460 include_apply_patch_tool : config. include_apply_patch_tool ,
463461 include_web_search_request : config. tools_web_search_request ,
@@ -1237,8 +1235,6 @@ async fn submission_loop(
12371235
12381236 let tools_config = ToolsConfig :: new ( & ToolsConfigParams {
12391237 model_family : & effective_family,
1240- approval_policy : new_approval_policy,
1241- sandbox_policy : new_sandbox_policy. clone ( ) ,
12421238 include_plan_tool : config. include_plan_tool ,
12431239 include_apply_patch_tool : config. include_apply_patch_tool ,
12441240 include_web_search_request : config. tools_web_search_request ,
@@ -1325,8 +1321,6 @@ async fn submission_loop(
13251321 client,
13261322 tools_config : ToolsConfig :: new ( & ToolsConfigParams {
13271323 model_family : & model_family,
1328- approval_policy,
1329- sandbox_policy : sandbox_policy. clone ( ) ,
13301324 include_plan_tool : config. include_plan_tool ,
13311325 include_apply_patch_tool : config. include_apply_patch_tool ,
13321326 include_web_search_request : config. tools_web_search_request ,
@@ -1553,8 +1547,6 @@ async fn spawn_review_thread(
15531547 . unwrap_or_else ( || parent_turn_context. client . get_model_family ( ) ) ;
15541548 let tools_config = ToolsConfig :: new ( & ToolsConfigParams {
15551549 model_family : & review_model_family,
1556- approval_policy : parent_turn_context. approval_policy ,
1557- sandbox_policy : parent_turn_context. sandbox_policy . clone ( ) ,
15581550 include_plan_tool : false ,
15591551 include_apply_patch_tool : config. include_apply_patch_tool ,
15601552 include_web_search_request : false ,
@@ -2724,6 +2716,21 @@ async fn handle_container_exec_with_params(
27242716 sub_id : String ,
27252717 call_id : String ,
27262718) -> ResponseInputItem {
2719+ if params. with_escalated_permissions . unwrap_or ( false )
2720+ && !matches ! ( turn_context. approval_policy, AskForApproval :: OnRequest )
2721+ {
2722+ return ResponseInputItem :: FunctionCallOutput {
2723+ call_id,
2724+ output : FunctionCallOutputPayload {
2725+ content : format ! (
2726+ "approval policy is {policy:?}; reject command — you should not ask for escalated permissions if the approval policy is {policy:?}" ,
2727+ policy = turn_context. approval_policy
2728+ ) ,
2729+ success : None ,
2730+ } ,
2731+ } ;
2732+ }
2733+
27272734 // check if this was a patch, and apply it if so
27282735 let apply_patch_exec = match maybe_parse_apply_patch_verified ( & params. command , & params. cwd ) {
27292736 MaybeApplyPatchVerified :: Body ( changes) => {
@@ -3345,6 +3352,7 @@ mod tests {
33453352 use mcp_types:: ContentBlock ;
33463353 use mcp_types:: TextContent ;
33473354 use pretty_assertions:: assert_eq;
3355+ use serde:: Deserialize ;
33483356 use serde_json:: json;
33493357 use std:: path:: PathBuf ;
33503358 use std:: sync:: Arc ;
@@ -3594,8 +3602,6 @@ mod tests {
35943602 ) ;
35953603 let tools_config = ToolsConfig :: new ( & ToolsConfigParams {
35963604 model_family : & config. model_family ,
3597- approval_policy : config. approval_policy ,
3598- sandbox_policy : config. sandbox_policy . clone ( ) ,
35993605 include_plan_tool : config. include_plan_tool ,
36003606 include_apply_patch_tool : config. include_apply_patch_tool ,
36013607 include_web_search_request : config. tools_web_search_request ,
@@ -3735,4 +3741,105 @@ mod tests {
37353741
37363742 ( rollout_items, live_history. contents ( ) )
37373743 }
3744+
3745+ #[ tokio:: test]
3746+ async fn rejects_escalated_permissions_when_policy_not_on_request ( ) {
3747+ use crate :: exec:: ExecParams ;
3748+ use crate :: protocol:: AskForApproval ;
3749+ use crate :: protocol:: SandboxPolicy ;
3750+ use crate :: turn_diff_tracker:: TurnDiffTracker ;
3751+ use std:: collections:: HashMap ;
3752+
3753+ let ( session, mut turn_context) = make_session_and_context ( ) ;
3754+ // Ensure policy is NOT OnRequest so the early rejection path triggers
3755+ turn_context. approval_policy = AskForApproval :: OnFailure ;
3756+
3757+ let params = ExecParams {
3758+ command : if cfg ! ( windows) {
3759+ vec ! [
3760+ "cmd.exe" . to_string( ) ,
3761+ "/C" . to_string( ) ,
3762+ "echo hi" . to_string( ) ,
3763+ ]
3764+ } else {
3765+ vec ! [
3766+ "/bin/sh" . to_string( ) ,
3767+ "-c" . to_string( ) ,
3768+ "echo hi" . to_string( ) ,
3769+ ]
3770+ } ,
3771+ cwd : turn_context. cwd . clone ( ) ,
3772+ timeout_ms : Some ( 1000 ) ,
3773+ env : HashMap :: new ( ) ,
3774+ with_escalated_permissions : Some ( true ) ,
3775+ justification : Some ( "test" . to_string ( ) ) ,
3776+ } ;
3777+
3778+ let params2 = ExecParams {
3779+ with_escalated_permissions : Some ( false ) ,
3780+ ..params. clone ( )
3781+ } ;
3782+
3783+ let mut turn_diff_tracker = TurnDiffTracker :: new ( ) ;
3784+
3785+ let sub_id = "test-sub" . to_string ( ) ;
3786+ let call_id = "test-call" . to_string ( ) ;
3787+
3788+ let resp = handle_container_exec_with_params (
3789+ params,
3790+ & session,
3791+ & turn_context,
3792+ & mut turn_diff_tracker,
3793+ sub_id,
3794+ call_id,
3795+ )
3796+ . await ;
3797+
3798+ let ResponseInputItem :: FunctionCallOutput { output, .. } = resp else {
3799+ panic ! ( "expected FunctionCallOutput" ) ;
3800+ } ;
3801+
3802+ let expected = format ! (
3803+ "approval policy is {policy:?}; reject command — you should not ask for escalated permissions if the approval policy is {policy:?}" ,
3804+ policy = turn_context. approval_policy
3805+ ) ;
3806+
3807+ pretty_assertions:: assert_eq!( output. content, expected) ;
3808+
3809+ // Now retry the same command WITHOUT escalated permissions; should succeed.
3810+ // Force DangerFullAccess to avoid platform sandbox dependencies in tests.
3811+ turn_context. sandbox_policy = SandboxPolicy :: DangerFullAccess ;
3812+
3813+ let resp2 = handle_container_exec_with_params (
3814+ params2,
3815+ & session,
3816+ & turn_context,
3817+ & mut turn_diff_tracker,
3818+ "test-sub" . to_string ( ) ,
3819+ "test-call-2" . to_string ( ) ,
3820+ )
3821+ . await ;
3822+
3823+ let ResponseInputItem :: FunctionCallOutput { output, .. } = resp2 else {
3824+ panic ! ( "expected FunctionCallOutput on retry" ) ;
3825+ } ;
3826+
3827+ #[ derive( Deserialize , PartialEq , Eq , Debug ) ]
3828+ struct ResponseExecMetadata {
3829+ exit_code : i32 ,
3830+ }
3831+
3832+ #[ derive( Deserialize ) ]
3833+ struct ResponseExecOutput {
3834+ output : String ,
3835+ metadata : ResponseExecMetadata ,
3836+ }
3837+
3838+ let exec_output: ResponseExecOutput =
3839+ serde_json:: from_str ( & output. content ) . expect ( "valid exec output json" ) ;
3840+
3841+ pretty_assertions:: assert_eq!( exec_output. metadata, ResponseExecMetadata { exit_code: 0 } ) ;
3842+ assert ! ( exec_output. output. contains( "hi" ) ) ;
3843+ pretty_assertions:: assert_eq!( output. success, Some ( true ) ) ;
3844+ }
37383845}
0 commit comments