@@ -55,17 +55,6 @@ impl ExecuteCommand {
5555 }
5656
5757 let default_arr = vec ! [ ] ;
58- let allowed_commands = allowed_commands. unwrap_or ( & default_arr) ;
59-
60- let has_regex_match = allowed_commands
61- . iter ( )
62- . map ( |cmd| Regex :: new ( & format ! ( r"\A{}\z" , cmd) ) )
63- . filter ( Result :: is_ok)
64- . flatten ( )
65- . any ( |regex| regex. is_match ( & self . command ) ) ;
66- if has_regex_match {
67- return false ;
68- }
6958
7059 let Some ( args) = shlex:: split ( & self . command ) else {
7160 return true ;
@@ -102,7 +91,7 @@ impl ExecuteCommand {
10291 }
10392
10493 // Check if each command in the pipe chain starts with a safe command
105- for cmd_args in all_commands {
94+ for cmd_args in & all_commands {
10695 match cmd_args. first ( ) {
10796 // Special casing for `find` so that we support most cases while safeguarding
10897 // against unwanted mutations
@@ -129,12 +118,29 @@ impl ExecuteCommand {
129118 {
130119 return true ;
131120 }
132- let is_cmd_read_only = READONLY_COMMANDS . contains ( & cmd. as_str ( ) ) ;
133- if !allow_read_only || !is_cmd_read_only {
134- return true ;
135- }
136121 } ,
137- None => return true ,
122+ None => { } ,
123+ }
124+ }
125+
126+ let allowed_commands = allowed_commands. unwrap_or ( & default_arr) ;
127+
128+ let has_regex_match = allowed_commands
129+ . iter ( )
130+ . map ( |cmd| Regex :: new ( & format ! ( r"\A{}\z" , cmd) ) )
131+ . filter ( Result :: is_ok)
132+ . flatten ( )
133+ . any ( |regex| regex. is_match ( & self . command ) ) ;
134+ if has_regex_match {
135+ return false ;
136+ }
137+
138+ for cmd_args in all_commands {
139+ if let Some ( cmd) = cmd_args. first ( ) {
140+ let is_cmd_read_only = READONLY_COMMANDS . contains ( & cmd. as_str ( ) ) ;
141+ if !allow_read_only || !is_cmd_read_only {
142+ return true ;
143+ }
138144 }
139145 }
140146
@@ -423,8 +429,19 @@ mod tests {
423429 ( "command subcommand a=0123456789 b=0123456789" , false ) ,
424430 ( "command subcommand a=0123456789 b=012345678" , true ) ,
425431 ( "command subcommand alternate a=0123456789 b=0123456789" , true ) ,
426- // Control characters ignored due to direct allowed_command_regex match
427- ( "command subcommand && command subcommand" , false ) ,
432+ // dangerous patterns
433+ ( "echo 'test<(data'" , true ) ,
434+ ( "echo 'test$(data)'" , true ) ,
435+ ( "echo 'test`data`'" , true ) ,
436+ ( "echo 'test' > output.txt" , true ) ,
437+ ( "echo 'test data' && touch main.py" , true ) ,
438+ ( "echo 'test' || rm file" , true ) ,
439+ ( "echo 'test' & background" , true ) ,
440+ ( "echo 'test data'; touch main.py" , true ) ,
441+ ( "echo $HOME" , true ) ,
442+ ( "echo 'test\n rm file'" , true ) ,
443+ ( "echo 'test\r rm file'" , true ) ,
444+ ( "IFS=/ malicious" , true ) ,
428445 ] ;
429446 for ( cmd, expected) in cmds {
430447 let tool = serde_json:: from_value :: < ExecuteCommand > ( serde_json:: json!( {
0 commit comments