@@ -48,6 +48,11 @@ pub struct ExecuteCommand {
4848
4949impl ExecuteCommand {
5050 pub fn requires_acceptance ( & self , allowed_commands : Option < & Vec < String > > , allow_read_only : bool ) -> bool {
51+ // Always require acceptance for multi-line commands.
52+ if self . command . contains ( "\n " ) || self . command . contains ( "\r " ) {
53+ return true ;
54+ }
55+
5156 let default_arr = vec ! [ ] ;
5257 let allowed_commands = allowed_commands. unwrap_or ( & default_arr) ;
5358
@@ -64,7 +69,7 @@ impl ExecuteCommand {
6469 let Some ( args) = shlex:: split ( & self . command ) else {
6570 return true ;
6671 } ;
67- const DANGEROUS_PATTERNS : & [ & str ] = & [ "<(" , "$(" , "`" , ">" , "&&" , "||" , "&" , ";" ] ;
72+ const DANGEROUS_PATTERNS : & [ & str ] = & [ "<(" , "$(" , "`" , ">" , "&&" , "||" , "&" , ";" , "${" , " \n " , " \r " , "IFS" ] ;
6873
6974 if args
7075 . iter ( )
@@ -106,6 +111,7 @@ impl ExecuteCommand {
106111 arg. contains ( "-exec" ) // includes -execdir
107112 || arg. contains ( "-delete" )
108113 || arg. contains ( "-ok" ) // includes -okdir
114+ || arg. contains ( "-fprint" ) // includes -fprint0 and -fprintf
109115 } ) =>
110116 {
111117 return true ;
@@ -114,7 +120,11 @@ impl ExecuteCommand {
114120 // Special casing for `grep`. -P flag for perl regexp has RCE issues, apparently
115121 // should not be supported within grep but is flagged as a possibility since this is perl
116122 // regexp.
117- if cmd == "grep" && cmd_args. iter ( ) . any ( |arg| arg. contains ( "-P" ) ) {
123+ if cmd == "grep"
124+ && cmd_args
125+ . iter ( )
126+ . any ( |arg| arg. contains ( "-P" ) || arg. contains ( "--perl-regexp" ) )
127+ {
118128 return true ;
119129 }
120130 let is_cmd_read_only = READONLY_COMMANDS . contains ( & cmd. as_str ( ) ) ;
@@ -293,6 +303,14 @@ mod tests {
293303 ( "cat <<< 'some string here' > myimportantfile" , true ) ,
294304 ( "echo '\n #!/usr/bin/env bash\n echo hello\n ' > myscript.sh" , true ) ,
295305 ( "cat <<EOF > myimportantfile\n hello world\n EOF" , true ) ,
306+ // newline checks
307+ ( "which ls\n touch asdf" , true ) ,
308+ ( "which ls\r touch asdf" , true ) ,
309+ // $IFS check
310+ (
311+ r#"IFS=';'; for cmd in "which ls;touch asdf"; do eval "$cmd"; done"# ,
312+ true ,
313+ ) ,
296314 // Safe piped commands
297315 ( "find . -name '*.rs' | grep main" , false ) ,
298316 ( "ls -la | grep .git" , false ) ,
@@ -310,8 +328,12 @@ mod tests {
310328 true ,
311329 ) ,
312330 ( "find important-dir/ -name '*.txt'" , false ) ,
331+ ( r#"find / -fprintf "/path/to/file" <data-to-write> -quit"# , true ) ,
332+ ( r"find . -${t}exec touch asdf \{\} +" , true ) ,
333+ ( r"find . -${t:=exec} touch asdf2 \{\} +" , true ) ,
313334 // `grep` command arguments
314335 ( "echo 'test data' | grep -P '(?{system(\" date\" )})'" , true ) ,
336+ ( "echo 'test data' | grep --perl-regexp '(?{system(\" date\" )})'" , true ) ,
315337 ] ;
316338 for ( cmd, expected) in cmds {
317339 let tool = serde_json:: from_value :: < ExecuteCommand > ( serde_json:: json!( {
0 commit comments