Skip to content

Commit 3a568d7

Browse files
fix: execute bash perms for potential unwanted execution (#444)
1 parent 4572eec commit 3a568d7

File tree

1 file changed

+71
-3
lines changed
  • crates/chat-cli/src/cli/chat/tools/execute

1 file changed

+71
-3
lines changed

crates/chat-cli/src/cli/chat/tools/execute/mod.rs

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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 {
162170
mod 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\necho hello\n' > myscript.sh", true),
197+
("cat <<EOF > myimportantfile\nhello world\nEOF", 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

Comments
 (0)