From 26791afd094c5b62c6af47ebce4e24eb7c2a3986 Mon Sep 17 00:00:00 2001 From: Dorien Koelemeijer Date: Thu, 14 May 2026 09:52:31 +1000 Subject: [PATCH 1/9] Update pattern matching to be a little less sensitive --- crates/goose/src/security/patterns.rs | 76 ++++++++++++++++++--------- 1 file changed, 50 insertions(+), 26 deletions(-) diff --git a/crates/goose/src/security/patterns.rs b/crates/goose/src/security/patterns.rs index df8173f54a46..d72264f496ca 100644 --- a/crates/goose/src/security/patterns.rs +++ b/crates/goose/src/security/patterns.rs @@ -53,13 +53,6 @@ pub const THREAT_PATTERNS: &[ThreatPattern] = &[ risk_level: RiskLevel::Critical, category: ThreatCategory::FileSystemDestruction, }, - ThreatPattern { - name: "rm_rf_system", - pattern: r"rm\s+(-[rf]*[rf][rf]*|--recursive|--force)\s+[^\s;|&]*/?(bin|etc|usr|var|sys|proc|dev|boot|lib|opt|srv)(?:/|\s|[;&|]|$)", - description: "Recursive deletion of system directories", - risk_level: RiskLevel::Critical, - category: ThreatCategory::FileSystemDestruction, - }, ThreatPattern { name: "dd_destruction", pattern: r"dd\s+.*if=/dev/(zero|random|urandom).*of=/dev/[sh]d[a-z]", @@ -242,14 +235,6 @@ pub const THREAT_PATTERNS: &[ThreatPattern] = &[ risk_level: RiskLevel::Medium, category: ThreatCategory::CommandInjection, }, - ThreatPattern { - name: "alternative_shell_invocation", - pattern: r"(/bin/|/usr/bin/|\./)?(bash|sh|zsh|fish|csh|tcsh|dash)\s+-c\s+.*[;&|]", - description: "Alternative shell invocation patterns", - risk_level: RiskLevel::Medium, - category: ThreatCategory::CommandInjection, - }, - // Additional dangerous commands that might be missing ThreatPattern { name: "docker_privileged_exec", pattern: r"docker\s+(run|exec).*--privileged", @@ -280,7 +265,9 @@ pub const THREAT_PATTERNS: &[ThreatPattern] = &[ }, ThreatPattern { name: "log_manipulation", - pattern: r"(>\s*/dev/null|truncate.*log|rm.*\.log|echo\s*>\s*/var/log)", + // Removed `> /dev/null` — stderr suppression (2>/dev/null) is standard shell + // and generated many false positives. Kept actual log tampering patterns. + pattern: r"(truncate.*log|rm\s+(-[rRfF]+\s+)*/var/log/|echo\s*>\s*/var/log)", description: "Log file manipulation or deletion", risk_level: RiskLevel::Medium, category: ThreatCategory::SystemModification, @@ -418,18 +405,55 @@ mod tests { } #[test] - fn rm_rf_system_matches_absolute_and_relative() { - let pat = "rm_rf_system"; - assert!(matches(pat, "rm -rf /etc")); - assert!(matches(pat, "rm -rf /usr/bin")); - assert!(matches(pat, "rm -rf etc")); - assert!(matches(pat, "rm -rf var")); + fn rm_rf_system_pattern_removed() { + let matcher = PatternMatcher::new(); + let commands = [ + "rm -rf /etc", + "rm -rf build", + "rm -f /tmp/file.json", + "rm -rf /opt/homebrew/Library/Taps/square/homebrew-formula", + ]; + for cmd in &commands { + let has_rm_rf_system = matcher + .scan_for_patterns(cmd) + .iter() + .any(|m| m.threat.name == "rm_rf_system"); + assert!(!has_rm_rf_system, "rm_rf_system should not exist: {cmd}"); + } } #[test] - fn rm_rf_system_no_false_positives() { - let pat = "rm_rf_system"; - assert!(!matches(pat, "rm -rf ./etc-backup")); - assert!(!matches(pat, "rm -rf /home/user/project")); + fn log_manipulation_no_dev_null_false_positives() { + let pat = "log_manipulation"; + // Standard stderr suppression should NOT match + assert!(!matches(pat, "ls 2>/dev/null")); + assert!(!matches(pat, "rm -f /tmp/file 2>/dev/null")); + assert!(!matches(pat, "command > /dev/null 2>&1")); + // Actual log tampering should still match + assert!(matches(pat, "truncate -s 0 /var/log/auth.log")); + assert!(matches(pat, "echo > /var/log/syslog")); + assert!(matches(pat, "rm -f /var/log/auth.log")); + assert!(matches(pat, "rm -rf /var/log/syslog")); + assert!(matches(pat, "rm -fr /var/log/auth.log")); + } + + #[test] + fn alternative_shell_invocation_removed() { + // alternative_shell_invocation was removed — ML classifier handles this. + let matcher = PatternMatcher::new(); + let commands = [ + r#"bash -c "echo hello && echo world""#, + r#"sh -c "ls | grep test""#, + ]; + for cmd in &commands { + let has_alt_shell = matcher + .scan_for_patterns(cmd) + .iter() + .any(|m| m.threat.name == "alternative_shell_invocation"); + assert!( + !has_alt_shell, + "alternative_shell_invocation should not exist: {cmd}" + ); + } } } From 4a2ccbb921ffaf48cb921680667122492f9b608d Mon Sep 17 00:00:00 2001 From: Dorien Koelemeijer Date: Thu, 14 May 2026 09:57:21 +1000 Subject: [PATCH 2/9] fix --- crates/goose/src/security/patterns.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/goose/src/security/patterns.rs b/crates/goose/src/security/patterns.rs index d72264f496ca..dcc5c5d665c5 100644 --- a/crates/goose/src/security/patterns.rs +++ b/crates/goose/src/security/patterns.rs @@ -265,8 +265,6 @@ pub const THREAT_PATTERNS: &[ThreatPattern] = &[ }, ThreatPattern { name: "log_manipulation", - // Removed `> /dev/null` — stderr suppression (2>/dev/null) is standard shell - // and generated many false positives. Kept actual log tampering patterns. pattern: r"(truncate.*log|rm\s+(-[rRfF]+\s+)*/var/log/|echo\s*>\s*/var/log)", description: "Log file manipulation or deletion", risk_level: RiskLevel::Medium, From 88ed696f2d1ae4ddfb799b389cca175b7e11bf38 Mon Sep 17 00:00:00 2001 From: Dorien Koelemeijer Date: Thu, 14 May 2026 10:06:03 +1000 Subject: [PATCH 3/9] catch bad rm -rf commands --- crates/goose/src/security/patterns.rs | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/crates/goose/src/security/patterns.rs b/crates/goose/src/security/patterns.rs index dcc5c5d665c5..49be1835a5a1 100644 --- a/crates/goose/src/security/patterns.rs +++ b/crates/goose/src/security/patterns.rs @@ -53,6 +53,13 @@ pub const THREAT_PATTERNS: &[ThreatPattern] = &[ risk_level: RiskLevel::Critical, category: ThreatCategory::FileSystemDestruction, }, + ThreatPattern { + name: "rm_rf_home_or_root", + pattern: r"rm\s+(-[rRfF]+\s+)*(-[rRfF]+|--recursive|--force|--no-preserve-root)(\s+(-[rRfF]+|--recursive|--force|--no-preserve-root))*\s+['\x22]?(~|\$HOME|/home|/root)/?['\x22]?(\s|[;&|]|$)", + description: "Recursive deletion of home or root directory", + risk_level: RiskLevel::Critical, + category: ThreatCategory::FileSystemDestruction, + }, ThreatPattern { name: "dd_destruction", pattern: r"dd\s+.*if=/dev/(zero|random|urandom).*of=/dev/[sh]d[a-z]", @@ -402,6 +409,36 @@ mod tests { assert!(!matches(pat, "rm -rf /tmp/cache")); } + #[test] + fn rm_rf_home_or_root_matches_bare_targets() { + let pat = "rm_rf_home_or_root"; + assert!(matches(pat, "rm -rf ~")); + assert!(matches(pat, "rm -rf ~/")); + assert!(matches(pat, "rm -rf $HOME")); + assert!(matches(pat, "rm -rf $HOME/")); + assert!(matches(pat, "rm -rf /home")); + assert!(matches(pat, "rm -rf /home/")); + assert!(matches(pat, "rm -rf /root")); + assert!(matches(pat, "rm -rf /root/")); + assert!(matches(pat, "rm -fr ~")); + assert!(matches(pat, "rm --recursive --force ~")); + assert!(matches(pat, r#"rm -rf "$HOME""#)); + assert!(matches(pat, "rm -rf ~; echo done")); + } + + #[test] + fn rm_rf_home_or_root_no_false_positives_on_subdirs() { + let pat = "rm_rf_home_or_root"; + assert!(!matches(pat, "rm -rf ~/Documents/my-gh-repo")); + assert!(!matches(pat, "rm -rf ~/.cache")); + assert!(!matches(pat, "rm -rf $HOME/build")); + assert!(!matches(pat, "rm -rf /home/user")); + assert!(!matches(pat, "rm -rf /home/user/project")); + assert!(!matches(pat, "rm -rf /root/tmp")); + assert!(!matches(pat, "rm -rf ./home")); + assert!(!matches(pat, "rm -rf $HOMEDIR")); + } + #[test] fn rm_rf_system_pattern_removed() { let matcher = PatternMatcher::new(); From aa8d3690f78dc455d1d6b13f76168b82f78414c1 Mon Sep 17 00:00:00 2001 From: Dorien Koelemeijer Date: Thu, 14 May 2026 10:12:05 +1000 Subject: [PATCH 4/9] more updates --- crates/goose/src/security/patterns.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/goose/src/security/patterns.rs b/crates/goose/src/security/patterns.rs index 49be1835a5a1..2219a5f661a6 100644 --- a/crates/goose/src/security/patterns.rs +++ b/crates/goose/src/security/patterns.rs @@ -272,7 +272,7 @@ pub const THREAT_PATTERNS: &[ThreatPattern] = &[ }, ThreatPattern { name: "log_manipulation", - pattern: r"(truncate.*log|rm\s+(-[rRfF]+\s+)*/var/log/|echo\s*>\s*/var/log)", + pattern: r"(truncate.*log|rm\s+((-[rRfF]+|--recursive|--force|--no-preserve-root)\s+)*/var/log(/|\s|[;&|]|$)|echo\s*>\s*/var/log)", description: "Log file manipulation or deletion", risk_level: RiskLevel::Medium, category: ThreatCategory::SystemModification, @@ -470,6 +470,12 @@ mod tests { assert!(matches(pat, "rm -f /var/log/auth.log")); assert!(matches(pat, "rm -rf /var/log/syslog")); assert!(matches(pat, "rm -fr /var/log/auth.log")); + assert!(matches(pat, "rm -rf /var/log")); + assert!(matches(pat, "rm --recursive --force /var/log/auth.log")); + assert!(matches(pat, "rm --recursive /var/log")); + // Similar-looking paths outside /var/log should NOT match + assert!(!matches(pat, "rm -rf /var/log-backup")); + assert!(!matches(pat, "rm -rf /var/logs")); } #[test] From aa5c9ab851ed164ca4d739c46385b25a6a41dad5 Mon Sep 17 00:00:00 2001 From: Dorien Koelemeijer Date: Thu, 14 May 2026 10:15:15 +1000 Subject: [PATCH 5/9] address comments --- crates/goose/src/security/patterns.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/goose/src/security/patterns.rs b/crates/goose/src/security/patterns.rs index 2219a5f661a6..6bd779f50155 100644 --- a/crates/goose/src/security/patterns.rs +++ b/crates/goose/src/security/patterns.rs @@ -55,7 +55,7 @@ pub const THREAT_PATTERNS: &[ThreatPattern] = &[ }, ThreatPattern { name: "rm_rf_home_or_root", - pattern: r"rm\s+(-[rRfF]+\s+)*(-[rRfF]+|--recursive|--force|--no-preserve-root)(\s+(-[rRfF]+|--recursive|--force|--no-preserve-root))*\s+['\x22]?(~|\$HOME|/home|/root)/?['\x22]?(\s|[;&|]|$)", + pattern: r"rm\s+(-[rRfF]+\s+)*(-[rRfF]+|--recursive|--force|--no-preserve-root)(\s+(-[rRfF]+|--recursive|--force|--no-preserve-root))*\s+['\x22]?(~|\$HOME|\$\{HOME\}|/home|/root)/?['\x22]?(\s|[;&|]|$)", description: "Recursive deletion of home or root directory", risk_level: RiskLevel::Critical, category: ThreatCategory::FileSystemDestruction, @@ -416,6 +416,8 @@ mod tests { assert!(matches(pat, "rm -rf ~/")); assert!(matches(pat, "rm -rf $HOME")); assert!(matches(pat, "rm -rf $HOME/")); + assert!(matches(pat, "rm -rf ${HOME}")); + assert!(matches(pat, r#"rm -rf "${HOME}""#)); assert!(matches(pat, "rm -rf /home")); assert!(matches(pat, "rm -rf /home/")); assert!(matches(pat, "rm -rf /root")); @@ -432,6 +434,8 @@ mod tests { assert!(!matches(pat, "rm -rf ~/Documents/my-gh-repo")); assert!(!matches(pat, "rm -rf ~/.cache")); assert!(!matches(pat, "rm -rf $HOME/build")); + assert!(!matches(pat, "rm -rf ${HOME}/build")); + assert!(!matches(pat, "rm -rf ${HOMEDIR}")); assert!(!matches(pat, "rm -rf /home/user")); assert!(!matches(pat, "rm -rf /home/user/project")); assert!(!matches(pat, "rm -rf /root/tmp")); From ccc6ff517360eb18a37c4e0872e9ac33eaa34b28 Mon Sep 17 00:00:00 2001 From: Dorien Koelemeijer Date: Thu, 14 May 2026 11:32:24 +1000 Subject: [PATCH 6/9] address more comments --- crates/goose/src/security/patterns.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/crates/goose/src/security/patterns.rs b/crates/goose/src/security/patterns.rs index 6bd779f50155..51ea9699f547 100644 --- a/crates/goose/src/security/patterns.rs +++ b/crates/goose/src/security/patterns.rs @@ -55,7 +55,7 @@ pub const THREAT_PATTERNS: &[ThreatPattern] = &[ }, ThreatPattern { name: "rm_rf_home_or_root", - pattern: r"rm\s+(-[rRfF]+\s+)*(-[rRfF]+|--recursive|--force|--no-preserve-root)(\s+(-[rRfF]+|--recursive|--force|--no-preserve-root))*\s+['\x22]?(~|\$HOME|\$\{HOME\}|/home|/root)/?['\x22]?(\s|[;&|]|$)", + pattern: r"rm(\s+(--[a-zA-Z][a-zA-Z\-]*|--|-[a-zA-Z]+))+\s+['\x22]?(~|\$HOME|\$\{HOME\}|/home|/root)/?(\*)?['\x22]?(\s|[;&|]|$)", description: "Recursive deletion of home or root directory", risk_level: RiskLevel::Critical, category: ThreatCategory::FileSystemDestruction, @@ -272,7 +272,7 @@ pub const THREAT_PATTERNS: &[ThreatPattern] = &[ }, ThreatPattern { name: "log_manipulation", - pattern: r"(truncate.*log|rm\s+((-[rRfF]+|--recursive|--force|--no-preserve-root)\s+)*/var/log(/|\s|[;&|]|$)|echo\s*>\s*/var/log)", + pattern: r"(truncate.*log|rm\s+((--[a-zA-Z][a-zA-Z\-]*|--|-[a-zA-Z]+)\s+)*/var/log(/|\s|[;&|]|$)|echo\s*>\s*/var/log)", description: "Log file manipulation or deletion", risk_level: RiskLevel::Medium, category: ThreatCategory::SystemModification, @@ -426,6 +426,16 @@ mod tests { assert!(matches(pat, "rm --recursive --force ~")); assert!(matches(pat, r#"rm -rf "$HOME""#)); assert!(matches(pat, "rm -rf ~; echo done")); + // Wildcard wipes of contents + assert!(matches(pat, "rm -rf /home/*")); + assert!(matches(pat, "rm -rf /root/*")); + assert!(matches(pat, "rm -rf ~/*")); + assert!(matches(pat, "rm -rf ${HOME}/*")); + assert!(matches(pat, r#"rm -rf "/home/*""#)); + // Extra flags and option separator + assert!(matches(pat, "rm -rfv ~")); + assert!(matches(pat, "rm -rf -- ~")); + assert!(matches(pat, "rm --recursive --force -- /home/*")); } #[test] @@ -441,6 +451,9 @@ mod tests { assert!(!matches(pat, "rm -rf /root/tmp")); assert!(!matches(pat, "rm -rf ./home")); assert!(!matches(pat, "rm -rf $HOMEDIR")); + // Wildcards inside subdirs should not match + assert!(!matches(pat, "rm -rf /home/user/*")); + assert!(!matches(pat, "rm -rf ~/Documents/*")); } #[test] @@ -477,6 +490,8 @@ mod tests { assert!(matches(pat, "rm -rf /var/log")); assert!(matches(pat, "rm --recursive --force /var/log/auth.log")); assert!(matches(pat, "rm --recursive /var/log")); + assert!(matches(pat, "rm -rf -- /var/log/auth.log")); + assert!(matches(pat, "rm -rfv /var/log/auth.log")); // Similar-looking paths outside /var/log should NOT match assert!(!matches(pat, "rm -rf /var/log-backup")); assert!(!matches(pat, "rm -rf /var/logs")); From f2ad2c34c48b3b125a157a326a8b8beda1b681b7 Mon Sep 17 00:00:00 2001 From: Dorien Koelemeijer Date: Thu, 14 May 2026 11:53:04 +1000 Subject: [PATCH 7/9] making more changes - bots comments are all contradicting each other, focus on what actually reduces FPs while still catching bad commands --- crates/goose/src/security/patterns.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/goose/src/security/patterns.rs b/crates/goose/src/security/patterns.rs index 51ea9699f547..76f3bb3b13c2 100644 --- a/crates/goose/src/security/patterns.rs +++ b/crates/goose/src/security/patterns.rs @@ -55,7 +55,7 @@ pub const THREAT_PATTERNS: &[ThreatPattern] = &[ }, ThreatPattern { name: "rm_rf_home_or_root", - pattern: r"rm(\s+(--[a-zA-Z][a-zA-Z\-]*|--|-[a-zA-Z]+))+\s+['\x22]?(~|\$HOME|\$\{HOME\}|/home|/root)/?(\*)?['\x22]?(\s|[;&|]|$)", + pattern: r"rm\s+((--[a-zA-Z][a-zA-Z\-]*|--|-[a-zA-Z]+)\s+)*(-[a-zA-Z]*[rR][a-zA-Z]*|--recursive|--dir|-d)(\s+(--[a-zA-Z][a-zA-Z\-]*|--|-[a-zA-Z]+))*\s+['\x22]?(~|\$HOME|\$\{HOME\}|/home|/root)/?(\*)?['\x22]?(\s|[;&|]|$)", description: "Recursive deletion of home or root directory", risk_level: RiskLevel::Critical, category: ThreatCategory::FileSystemDestruction, @@ -454,6 +454,14 @@ mod tests { // Wildcards inside subdirs should not match assert!(!matches(pat, "rm -rf /home/user/*")); assert!(!matches(pat, "rm -rf ~/Documents/*")); + // Flags that cannot recursively remove directories should not fire + assert!(!matches(pat, "rm -i /root")); + assert!(!matches(pat, "rm -f ~")); + assert!(!matches(pat, "rm --force ~")); + assert!(!matches(pat, "rm --help ~")); + assert!(!matches(pat, "rm -v ~")); + assert!(!matches(pat, "rm -- ~")); + assert!(!matches(pat, "rm ~")); } #[test] From 5c01c94b658fae7063cc1a2846e9d7cbec3531f5 Mon Sep 17 00:00:00 2001 From: Dorien Koelemeijer Date: Thu, 14 May 2026 12:02:01 +1000 Subject: [PATCH 8/9] remove unnecessary tests --- crates/goose/src/security/patterns.rs | 37 --------------------------- 1 file changed, 37 deletions(-) diff --git a/crates/goose/src/security/patterns.rs b/crates/goose/src/security/patterns.rs index 76f3bb3b13c2..d355e6c8e652 100644 --- a/crates/goose/src/security/patterns.rs +++ b/crates/goose/src/security/patterns.rs @@ -464,24 +464,6 @@ mod tests { assert!(!matches(pat, "rm ~")); } - #[test] - fn rm_rf_system_pattern_removed() { - let matcher = PatternMatcher::new(); - let commands = [ - "rm -rf /etc", - "rm -rf build", - "rm -f /tmp/file.json", - "rm -rf /opt/homebrew/Library/Taps/square/homebrew-formula", - ]; - for cmd in &commands { - let has_rm_rf_system = matcher - .scan_for_patterns(cmd) - .iter() - .any(|m| m.threat.name == "rm_rf_system"); - assert!(!has_rm_rf_system, "rm_rf_system should not exist: {cmd}"); - } - } - #[test] fn log_manipulation_no_dev_null_false_positives() { let pat = "log_manipulation"; @@ -505,23 +487,4 @@ mod tests { assert!(!matches(pat, "rm -rf /var/logs")); } - #[test] - fn alternative_shell_invocation_removed() { - // alternative_shell_invocation was removed — ML classifier handles this. - let matcher = PatternMatcher::new(); - let commands = [ - r#"bash -c "echo hello && echo world""#, - r#"sh -c "ls | grep test""#, - ]; - for cmd in &commands { - let has_alt_shell = matcher - .scan_for_patterns(cmd) - .iter() - .any(|m| m.threat.name == "alternative_shell_invocation"); - assert!( - !has_alt_shell, - "alternative_shell_invocation should not exist: {cmd}" - ); - } - } } From 48188acb76eab813bdf4460797f713abc4cabbfb Mon Sep 17 00:00:00 2001 From: Dorien Koelemeijer Date: Thu, 14 May 2026 12:10:18 +1000 Subject: [PATCH 9/9] lint --- crates/goose/src/security/patterns.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/goose/src/security/patterns.rs b/crates/goose/src/security/patterns.rs index d355e6c8e652..75bef0f4bb2d 100644 --- a/crates/goose/src/security/patterns.rs +++ b/crates/goose/src/security/patterns.rs @@ -486,5 +486,4 @@ mod tests { assert!(!matches(pat, "rm -rf /var/log-backup")); assert!(!matches(pat, "rm -rf /var/logs")); } - }