@@ -456,8 +456,6 @@ impl Session {
456
456
client,
457
457
tools_config : ToolsConfig :: new ( & ToolsConfigParams {
458
458
model_family : & config. model_family ,
459
- approval_policy,
460
- sandbox_policy : sandbox_policy. clone ( ) ,
461
459
include_plan_tool : config. include_plan_tool ,
462
460
include_apply_patch_tool : config. include_apply_patch_tool ,
463
461
include_web_search_request : config. tools_web_search_request ,
@@ -1237,8 +1235,6 @@ async fn submission_loop(
1237
1235
1238
1236
let tools_config = ToolsConfig :: new ( & ToolsConfigParams {
1239
1237
model_family : & effective_family,
1240
- approval_policy : new_approval_policy,
1241
- sandbox_policy : new_sandbox_policy. clone ( ) ,
1242
1238
include_plan_tool : config. include_plan_tool ,
1243
1239
include_apply_patch_tool : config. include_apply_patch_tool ,
1244
1240
include_web_search_request : config. tools_web_search_request ,
@@ -1325,8 +1321,6 @@ async fn submission_loop(
1325
1321
client,
1326
1322
tools_config : ToolsConfig :: new ( & ToolsConfigParams {
1327
1323
model_family : & model_family,
1328
- approval_policy,
1329
- sandbox_policy : sandbox_policy. clone ( ) ,
1330
1324
include_plan_tool : config. include_plan_tool ,
1331
1325
include_apply_patch_tool : config. include_apply_patch_tool ,
1332
1326
include_web_search_request : config. tools_web_search_request ,
@@ -1553,8 +1547,6 @@ async fn spawn_review_thread(
1553
1547
. unwrap_or_else ( || parent_turn_context. client . get_model_family ( ) ) ;
1554
1548
let tools_config = ToolsConfig :: new ( & ToolsConfigParams {
1555
1549
model_family : & review_model_family,
1556
- approval_policy : parent_turn_context. approval_policy ,
1557
- sandbox_policy : parent_turn_context. sandbox_policy . clone ( ) ,
1558
1550
include_plan_tool : false ,
1559
1551
include_apply_patch_tool : config. include_apply_patch_tool ,
1560
1552
include_web_search_request : false ,
@@ -2724,6 +2716,21 @@ async fn handle_container_exec_with_params(
2724
2716
sub_id : String ,
2725
2717
call_id : String ,
2726
2718
) -> 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
+
2727
2734
// check if this was a patch, and apply it if so
2728
2735
let apply_patch_exec = match maybe_parse_apply_patch_verified ( & params. command , & params. cwd ) {
2729
2736
MaybeApplyPatchVerified :: Body ( changes) => {
@@ -3345,6 +3352,7 @@ mod tests {
3345
3352
use mcp_types:: ContentBlock ;
3346
3353
use mcp_types:: TextContent ;
3347
3354
use pretty_assertions:: assert_eq;
3355
+ use serde:: Deserialize ;
3348
3356
use serde_json:: json;
3349
3357
use std:: path:: PathBuf ;
3350
3358
use std:: sync:: Arc ;
@@ -3594,8 +3602,6 @@ mod tests {
3594
3602
) ;
3595
3603
let tools_config = ToolsConfig :: new ( & ToolsConfigParams {
3596
3604
model_family : & config. model_family ,
3597
- approval_policy : config. approval_policy ,
3598
- sandbox_policy : config. sandbox_policy . clone ( ) ,
3599
3605
include_plan_tool : config. include_plan_tool ,
3600
3606
include_apply_patch_tool : config. include_apply_patch_tool ,
3601
3607
include_web_search_request : config. tools_web_search_request ,
@@ -3735,4 +3741,105 @@ mod tests {
3735
3741
3736
3742
( rollout_items, live_history. contents ( ) )
3737
3743
}
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
+ }
3738
3845
}
0 commit comments