@@ -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 ( ) ) ;
@@ -290,6 +300,14 @@ mod tests {
290300 ( "cat <<< 'some string here' > myimportantfile" , true ) ,
291301 ( "echo '\n #!/usr/bin/env bash\n echo hello\n ' > myscript.sh" , true ) ,
292302 ( "cat <<EOF > myimportantfile\n hello world\n EOF" , true ) ,
303+ // newline checks
304+ ( "which ls\n touch asdf" , true ) ,
305+ ( "which ls\r touch asdf" , true ) ,
306+ // $IFS check
307+ (
308+ r#"IFS=';'; for cmd in "which ls;touch asdf"; do eval "$cmd"; done"# ,
309+ true ,
310+ ) ,
293311 // Safe piped commands
294312 ( "find . -name '*.rs' | grep main" , false ) ,
295313 ( "ls -la | grep .git" , false ) ,
@@ -307,8 +325,12 @@ mod tests {
307325 true ,
308326 ) ,
309327 ( "find important-dir/ -name '*.txt'" , false ) ,
328+ ( r#"find / -fprintf "/path/to/file" <data-to-write> -quit"# , true ) ,
329+ ( r"find . -${t}exec touch asdf \{\} +" , true ) ,
330+ ( r"find . -${t:=exec} touch asdf2 \{\} +" , true ) ,
310331 // `grep` command arguments
311332 ( "echo 'test data' | grep -P '(?{system(\" date\" )})'" , true ) ,
333+ ( "echo 'test data' | grep --perl-regexp '(?{system(\" date\" )})'" , true ) ,
312334 ] ;
313335 for ( cmd, expected) in cmds {
314336 let tool = serde_json:: from_value :: < ExecuteCommand > ( serde_json:: json!( {
0 commit comments