Skip to content

Commit ac255a8

Browse files
committed
Fix flaky command safety test by using robust regex matching
1 parent 401d626 commit ac255a8

1 file changed

Lines changed: 42 additions & 25 deletions

File tree

src/tools/bash/safety.rs

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@ use std::sync::LazyLock;
1010
pub static BLOCKED_PATTERNS: LazyLock<Vec<Regex>> = LazyLock::new(|| {
1111
vec![
1212
// Destructive filesystem operations
13-
Regex::new(r"rm\s+(-[rfRF]+\s+)*[/~](\s|$)").unwrap(),
14-
Regex::new(r"rm\s+(-[rfRF]+\s+)*/\*").unwrap(),
15-
Regex::new(r"rm\s+(-[rfRF]+\s+)*~").unwrap(),
13+
Regex::new(r"(?m)(^|[;&|]\s*)rm\s+(-[rfRF]+\s+)*[/~](\s|$)").unwrap(),
14+
Regex::new(r"(?m)(^|[;&|]\s*)rm\s+(-[rfRF]+\s+)*/\*").unwrap(),
15+
Regex::new(r"(?m)(^|[;&|]\s*)rm\s+(-[rfRF]+\s+)*~").unwrap(),
1616
// Disk/device operations
17-
Regex::new(r"dd\s+.*if=").unwrap(),
18-
Regex::new(r"mkfs").unwrap(),
17+
Regex::new(r"(?m)(^|[;&|]\s*)dd\s+.*if=").unwrap(),
18+
Regex::new(r"(?m)(^|[;&|]\s*)mkfs").unwrap(),
1919
Regex::new(r">\s*/dev/sd").unwrap(),
2020
Regex::new(r">\s*/dev/nvme").unwrap(),
2121
// Permission bombs
22-
Regex::new(r"chmod\s+(-[rR]+\s+)*777\s+/").unwrap(),
23-
Regex::new(r"chown\s+(-[rR]+\s+)*.*\s+/").unwrap(),
22+
Regex::new(r"(?m)(^|[;&|]\s*)chmod\s+(-[rR]+\s+)*777\s+/").unwrap(),
23+
Regex::new(r"(?m)(^|[;&|]\s*)chown\s+(-[rR]+\s+)*.*\s+/").unwrap(),
2424
// Fork bomb
2525
Regex::new(r":\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;").unwrap(),
2626
// Dangerous redirects
@@ -34,24 +34,18 @@ pub static BLOCKED_PATTERNS: LazyLock<Vec<Regex>> = LazyLock::new(|| {
3434
});
3535

3636
/// Commands that require extra caution (requires user confirmation).
37-
pub static CAUTION_PATTERNS: LazyLock<Vec<&str>> = LazyLock::new(|| {
37+
pub static CAUTION_PATTERNS: LazyLock<Vec<Regex>> = LazyLock::new(|| {
3838
vec![
39-
"sudo",
40-
"su ",
41-
"rm ",
42-
"mv ",
43-
"chmod",
44-
"chown",
45-
"kill",
46-
"pkill",
47-
"killall",
48-
"git push --force",
49-
"git push -f",
50-
"git reset --hard",
51-
"cargo publish",
52-
"npm publish",
53-
"docker rm",
54-
"docker rmi",
39+
// Dangerous binaries (word boundary safe)
40+
// Matches start of line or after command separator (; | &)
41+
Regex::new(r"(?m)(^|[;&|]\s*)(sudo|su|rm|mv|chmod|chown|kill|pkill|killall)(\s+|$)").unwrap(),
42+
43+
// Dangerous subcommands
44+
Regex::new(r"(?m)(^|[;&|]\s*)(docker\s+(rm|rmi)|cargo\s+publish|npm\s+publish)(\s+|$)").unwrap(),
45+
46+
// Dangerous flags
47+
Regex::new(r"git\s+push\s+.*(-f|--force)").unwrap(),
48+
Regex::new(r"git\s+reset\s+.*--hard").unwrap(),
5549
]
5650
});
5751

@@ -70,7 +64,7 @@ pub fn is_blocked(command: &str) -> Option<String> {
7064
pub fn needs_caution(command: &str) -> bool {
7165
CAUTION_PATTERNS
7266
.iter()
73-
.any(|pattern| command.contains(pattern))
67+
.any(|pattern| pattern.is_match(command))
7468
}
7569

7670
#[cfg(test)]
@@ -92,6 +86,12 @@ mod tests {
9286
assert!(is_blocked("ls -l").is_none());
9387
}
9488

89+
#[test]
90+
fn test_blocked_false_positives() {
91+
assert!(is_blocked("echo harm -rf /").is_none(), "Should not block 'harm'");
92+
assert!(is_blocked("echo farm -rf /*").is_none(), "Should not block 'farm'");
93+
}
94+
9595
#[test]
9696
fn test_caution_patterns() {
9797
assert!(needs_caution("sudo apt update"));
@@ -100,4 +100,21 @@ mod tests {
100100
assert!(needs_caution("git reset --hard HEAD"));
101101
assert!(!needs_caution("ls -l"));
102102
}
103+
104+
#[test]
105+
fn test_caution_patterns_edge_cases() {
106+
assert!(needs_caution("rm\tfile.txt"), "Should catch tab separator");
107+
assert!(needs_caution("rm\nfile.txt"), "Should catch newline separator");
108+
assert!(needs_caution("echo hi; rm file"), "Should catch rm after semicolon");
109+
assert!(needs_caution("echo hi | rm file"), "Should catch rm after pipe");
110+
assert!(needs_caution("rm"), "Should catch bare rm");
111+
}
112+
113+
#[test]
114+
fn test_caution_false_positives() {
115+
assert!(!needs_caution("echo farm animal"), "Should not flag 'farm' as dangerous");
116+
assert!(!needs_caution("echo supper"), "Should not flag 'supper' (contains 'su')");
117+
assert!(!needs_caution("mkdir form"), "Should not flag 'form' (matches 'rm' substring)");
118+
assert!(!needs_caution("echo remove"), "Should not flag 'remove'");
119+
}
103120
}

0 commit comments

Comments
 (0)