@@ -81,12 +81,20 @@ impl ExecuteCommand {
8181 // against unwanted mutations
8282 Some ( cmd)
8383 if cmd == "find"
84- && cmd_args
85- . iter ( )
86- . any ( |arg| arg. contains ( "-exec" ) || arg. contains ( "-delete" ) ) =>
84+ && cmd_args. iter ( ) . any ( |arg| {
85+ arg. contains ( "-exec" ) // includes -execdir
86+ || arg. contains ( "-delete" )
87+ || arg. contains ( "-ok" ) // includes -okdir
88+ } ) =>
8789 {
8890 return true ;
8991 } ,
92+ // Special casing for `grep`. -P flag for perl regexp has RCE issues, apparently
93+ // should not be supported within grep but is flagged as a possibility since this is perl
94+ // regexp.
95+ Some ( cmd) if cmd == "grep" && cmd_args. iter ( ) . any ( |arg| arg. contains ( "-P" ) ) => {
96+ return true ;
97+ } ,
9098 Some ( cmd) if !READONLY_COMMANDS . contains ( & cmd. as_str ( ) ) => return true ,
9199 None => return true ,
92100 _ => ( ) ,
@@ -162,6 +170,66 @@ pub fn format_output(output: &str, max_size: usize) -> String {
162170mod tests {
163171 use super :: * ;
164172
173+ #[ test]
174+ fn test_requires_acceptance_for_readonly_commands ( ) {
175+ let cmds = & [
176+ // Safe commands
177+ ( "ls ~" , false ) ,
178+ ( "ls -al ~" , false ) ,
179+ ( "pwd" , false ) ,
180+ ( "echo 'Hello, world!'" , false ) ,
181+ ( "which aws" , false ) ,
182+ // Potentially dangerous readonly commands
183+ ( "echo hi > myimportantfile" , true ) ,
184+ ( "ls -al >myimportantfile" , true ) ,
185+ ( "echo hi 2> myimportantfile" , true ) ,
186+ ( "echo hi >> myimportantfile" , true ) ,
187+ ( "echo $(rm myimportantfile)" , true ) ,
188+ ( "echo `rm myimportantfile`" , true ) ,
189+ ( "echo hello && rm myimportantfile" , true ) ,
190+ ( "echo hello&&rm myimportantfile" , true ) ,
191+ ( "ls nonexistantpath || rm myimportantfile" , true ) ,
192+ ( "echo myimportantfile | xargs rm" , true ) ,
193+ ( "echo myimportantfile|args rm" , true ) ,
194+ ( "echo <(rm myimportantfile)" , true ) ,
195+ ( "cat <<< 'some string here' > myimportantfile" , true ) ,
196+ ( "echo '\n #!/usr/bin/env bash\n echo hello\n ' > myscript.sh" , true ) ,
197+ ( "cat <<EOF > myimportantfile\n hello world\n EOF" , true ) ,
198+ // Safe piped commands
199+ ( "find . -name '*.rs' | grep main" , false ) ,
200+ ( "ls -la | grep .git" , false ) ,
201+ ( "cat file.txt | grep pattern | head -n 5" , false ) ,
202+ // Unsafe piped commands
203+ ( "find . -name '*.rs' | rm" , true ) ,
204+ ( "ls -la | grep .git | rm -rf" , true ) ,
205+ ( "echo hello | sudo rm -rf /" , true ) ,
206+ // `find` command arguments
207+ ( "find important-dir/ -exec rm {} \\ ;" , true ) ,
208+ ( "find . -name '*.c' -execdir gcc -o '{}.out' '{}' \\ ;" , true ) ,
209+ ( "find important-dir/ -delete" , true ) ,
210+ (
211+ "echo y | find . -type f -maxdepth 1 -okdir open -a Calculator {} +" ,
212+ true ,
213+ ) ,
214+ ( "find important-dir/ -name '*.txt'" , false ) ,
215+ // `grep` command arguments
216+ ( "echo 'test data' | grep -P '(?{system(\" date\" )})'" , true ) ,
217+ ] ;
218+ for ( cmd, expected) in cmds {
219+ let tool = serde_json:: from_value :: < ExecuteCommand > ( serde_json:: json!( {
220+ "command" : cmd,
221+ } ) )
222+ . unwrap ( ) ;
223+ assert_eq ! (
224+ tool. requires_acceptance( ) ,
225+ * expected,
226+ "expected command: `{}` to have requires_acceptance: `{}`" ,
227+ cmd,
228+ expected
229+ ) ;
230+ }
231+ }
232+
165233 #[ test]
166234 fn test_requires_acceptance_for_windows_commands ( ) {
167235 let cmds = & [
0 commit comments