@@ -194,14 +194,12 @@ impl ExecuteCommand {
194
194
195
195
let Self { command, .. } = self ;
196
196
let tool_name = if cfg ! ( windows) { "execute_cmd" } else { "execute_bash" } ;
197
- let is_in_allowlist = agent. allowed_tools . contains ( tool_name) ;
198
- match agent. tools_settings . get ( tool_name) {
199
- Some ( settings) if is_in_allowlist => {
200
- let Settings {
201
- allowed_commands,
202
- denied_commands,
203
- allow_read_only,
204
- } = match serde_json:: from_value :: < Settings > ( settings. clone ( ) ) {
197
+ let is_in_allowlist = agent. allowed_tools . contains ( "execute_bash" ) ;
198
+
199
+ // Step 1: If execute_bash is in allowedTools, always allow (except denied commands)
200
+ if is_in_allowlist {
201
+ if let Some ( settings) = agent. tools_settings . get ( tool_name) {
202
+ let Settings { denied_commands, .. } = match serde_json:: from_value :: < Settings > ( settings. clone ( ) ) {
205
203
Ok ( settings) => settings,
206
204
Err ( e) => {
207
205
error ! ( "Failed to deserialize tool settings for execute_bash: {:?}" , e) ;
@@ -219,15 +217,54 @@ impl ExecuteCommand {
219
217
if !denied_match_set. is_empty ( ) {
220
218
return PermissionEvalResult :: Deny ( denied_match_set) ;
221
219
}
220
+ }
221
+ return PermissionEvalResult :: Allow ;
222
+ }
223
+
224
+ // Step 2: Check if command matches denied patterns
225
+ if let Some ( settings) = agent. tools_settings . get ( tool_name) {
226
+ let Settings { denied_commands, .. } = match serde_json:: from_value :: < Settings > ( settings. clone ( ) ) {
227
+ Ok ( settings) => settings,
228
+ Err ( e) => {
229
+ error ! ( "Failed to deserialize tool settings for execute_bash: {:?}" , e) ;
230
+ return PermissionEvalResult :: Ask ;
231
+ } ,
232
+ } ;
233
+
234
+ let denied_match_set = denied_commands
235
+ . iter ( )
236
+ . filter_map ( |dc| Regex :: new ( & format ! ( r"\A{dc}\z" ) ) . ok ( ) )
237
+ . filter ( |r| r. is_match ( command) )
238
+ . map ( |r| r. to_string ( ) )
239
+ . collect :: < Vec < _ > > ( ) ;
240
+
241
+ if !denied_match_set. is_empty ( ) {
242
+ return PermissionEvalResult :: Deny ( denied_match_set) ;
243
+ }
244
+ }
245
+
246
+ // Step 3: Use requires_acceptance logic with allowedCommands patterns
247
+ match agent. tools_settings . get ( tool_name) {
248
+ Some ( settings) => {
249
+ let Settings {
250
+ allowed_commands,
251
+ allow_read_only,
252
+ ..
253
+ } = match serde_json:: from_value :: < Settings > ( settings. clone ( ) ) {
254
+ Ok ( settings) => settings,
255
+ Err ( e) => {
256
+ error ! ( "Failed to deserialize tool settings for execute_bash: {:?}" , e) ;
257
+ return PermissionEvalResult :: Ask ;
258
+ } ,
259
+ } ;
222
260
223
261
if self . requires_acceptance ( Some ( & allowed_commands) , allow_read_only) {
224
262
PermissionEvalResult :: Ask
225
263
} else {
226
264
PermissionEvalResult :: Allow
227
265
}
228
266
} ,
229
- None if is_in_allowlist => PermissionEvalResult :: Allow ,
230
- _ => {
267
+ None => {
231
268
if self . requires_acceptance ( None , default_allow_read_only ( ) ) {
232
269
PermissionEvalResult :: Ask
233
270
} else {
@@ -400,6 +437,7 @@ mod tests {
400
437
}
401
438
402
439
#[ test]
440
+ <<<<<<< HEAD
403
441
fn test_eval_perm( ) {
404
442
let tool_name = if cfg ! ( windows) { "execute_cmd" } else { "execute_bash" } ;
405
443
let agent = Agent {
@@ -486,5 +524,150 @@ mod tests {
486
524
// Should contain both the existing value and our metadata
487
525
assert ! ( user_agent_value. contains( "ExistingValue" ) ) ;
488
526
assert ! ( user_agent_value. contains( USER_AGENT_APP_NAME ) ) ;
527
+ =======
528
+ fn test_eval_perm_allowed_tools_priority( ) {
529
+ use crate:: cli:: agent:: { Agent, PermissionEvalResult} ;
530
+
531
+ // Test that when execute_bash is in allowedTools, it always allows (except denied commands)
532
+ let agent_json = serde_json:: json!( {
533
+ "name" : "test_agent" ,
534
+ "allowedTools" : [ "execute_bash" ] ,
535
+ "toolsSettings" : {
536
+ "execute_bash" : {
537
+ "allowedCommands" : [ "ls" ] ,
538
+ "deniedCommands" : [ "rm" ] ,
539
+ "allowReadOnly" : false
540
+ }
541
+ }
542
+ } ) ;
543
+
544
+ let agent: Agent = serde_json:: from_value ( agent_json ) . unwrap ( ) ;
545
+
546
+ let dangerous_cmd = ExecuteCommand {
547
+ command : "sudo dd if=/dev/zero of=/dev/sda" . to_string ( ) ,
548
+ summary : None ,
549
+ } ;
550
+
551
+ let denied_cmd = ExecuteCommand {
552
+ command : "rm file.txt" . to_string( ) ,
553
+ summary : None ,
554
+ } ;
555
+
556
+ // Should allow dangerous command because execute_bash is in allowedTools
557
+ assert_eq ! ( dangerous_cmd. eval_perm( & agent) , PermissionEvalResult :: Allow ) ;
558
+
559
+ // Should deny command matching denied pattern
560
+ assert ! ( matches!( denied_cmd. eval_perm( & agent) , PermissionEvalResult :: Deny ( _) ) ) ;
561
+ }
562
+
563
+ #[ test]
564
+ fn test_eval_perm_fine_grained_control( ) {
565
+ use crate:: cli:: agent:: { Agent, PermissionEvalResult} ;
566
+
567
+ // Test fine-grained control when execute_bash is NOT in allowedTools
568
+ let agent_json = serde_json:: json!( {
569
+ "name" : "test_agent" ,
570
+ "allowedTools" : [ ] ,
571
+ "toolsSettings" : {
572
+ "execute_bash" : {
573
+ "allowedCommands" : [ "git status" , "ls.*" ] ,
574
+ "deniedCommands" : [ "rm" ] ,
575
+ "allowReadOnly" : true
576
+ }
577
+ }
578
+ } ) ;
579
+
580
+ let agent: Agent = serde_json:: from_value ( agent_json ) . unwrap ( ) ;
581
+
582
+ let allowed_cmd = ExecuteCommand {
583
+ command : "git status". to_string ( ) ,
584
+ summary : None ,
585
+ } ;
586
+
587
+ let pattern_match_cmd = ExecuteCommand {
588
+ command : "ls -la" . to_string( ) ,
589
+ summary : None ,
590
+ } ;
591
+
592
+ let readonly_cmd = ExecuteCommand {
593
+ command : "cat file.txt" . to_string( ) ,
594
+ summary : None ,
595
+ } ;
596
+
597
+ let denied_cmd = ExecuteCommand {
598
+ command : "rm file.txt" . to_string( ) ,
599
+ summary : None ,
600
+ } ;
601
+
602
+ let dangerous_cmd = ExecuteCommand {
603
+ command : "sudo dd if=/dev/zero of=/dev/sda" . to_string( ) ,
604
+ summary : None ,
605
+ } ;
606
+
607
+ // Should allow command matching allowedCommands pattern
608
+ assert_eq ! ( allowed_cmd. eval_perm( & agent) , PermissionEvalResult :: Allow ) ;
609
+
610
+ // Should allow command matching regex pattern
611
+ assert_eq ! ( pattern_match_cmd. eval_perm( & agent) , PermissionEvalResult :: Allow ) ;
612
+
613
+ // Should allow readonly command when allowReadOnly is true
614
+ assert_eq ! ( readonly_cmd. eval_perm( & agent) , PermissionEvalResult :: Allow ) ;
615
+
616
+ // Should deny command matching denied pattern
617
+ assert ! ( matches!( denied_cmd. eval_perm( & agent) , PermissionEvalResult :: Deny ( _) ) ) ;
618
+
619
+ // Should ask for dangerous command not matching any allowed pattern
620
+ assert_eq ! ( dangerous_cmd. eval_perm( & agent) , PermissionEvalResult :: Ask ) ;
621
+ }
622
+
623
+ #[ test]
624
+ fn test_eval_perm_default_behavior( ) {
625
+ use crate:: cli:: agent:: { Agent, PermissionEvalResult} ;
626
+
627
+ // Test default behavior when no settings are configured
628
+ let agent = Agent :: default ( ) ;
629
+
630
+ let readonly_cmd = ExecuteCommand {
631
+ command : "ls -la" . to_string( ) ,
632
+ summary : None ,
633
+ } ;
634
+
635
+ let dangerous_cmd = ExecuteCommand {
636
+ command : "sudo rm -rf /" . to_string( ) ,
637
+ summary : None ,
638
+ } ;
639
+
640
+ // Should allow readonly command with default settings
641
+ assert_eq ! ( readonly_cmd. eval_perm( & agent) , PermissionEvalResult :: Allow ) ;
642
+
643
+ // Should ask for dangerous command with default settings
644
+ assert_eq ! ( dangerous_cmd. eval_perm( & agent) , PermissionEvalResult :: Ask ) ;
645
+ }
646
+
647
+ #[ test]
648
+ fn test_eval_perm_invalid_settings( ) {
649
+ use crate:: cli:: agent:: { Agent, PermissionEvalResult} ;
650
+
651
+ // Test behavior with invalid JSON settings
652
+ let agent_json = serde_json:: json!( {
653
+ "name" : "test_agent" ,
654
+ "allowedTools" : [ ] ,
655
+ "toolsSettings" : {
656
+ "execute_bash" : {
657
+ "invalidField" : "invalidValue"
658
+ }
659
+ }
660
+ } ) ;
661
+
662
+ let agent : Agent = serde_json:: from_value ( agent_json ) . unwrap ( ) ;
663
+
664
+ let cmd = ExecuteCommand {
665
+ command : "sudo systemctl restart nginx" . to_string ( ) ,
666
+ summary : None ,
667
+ } ;
668
+
669
+ // Should ask when settings are invalid (falls back to safe behavior)
670
+ assert_eq ! ( cmd. eval_perm( & agent) , PermissionEvalResult :: Ask ) ;
671
+ >>>>>>> 20 b287db ( fix : enhance execute tool permission logic for fine-grained command control )
489
672
}
490
673
}
0 commit comments