Skip to content

Commit a62c80c

Browse files
fix: add more safety checks for execute_bash readonly checks (#2605)
1 parent dfaa580 commit a62c80c

File tree

1 file changed

+24
-2
lines changed
  • crates/chat-cli/src/cli/chat/tools/execute

1 file changed

+24
-2
lines changed

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ pub struct ExecuteCommand {
4848

4949
impl 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\necho hello\n' > myscript.sh", true),
295305
("cat <<EOF > myimportantfile\nhello world\nEOF", true),
306+
// newline checks
307+
("which ls\ntouch asdf", true),
308+
("which ls\rtouch 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

Comments
 (0)