diff --git a/crates/goose/src/security/patterns.rs b/crates/goose/src/security/patterns.rs index 2dee8e907a..0fec503434 100644 --- a/crates/goose/src/security/patterns.rs +++ b/crates/goose/src/security/patterns.rs @@ -232,13 +232,13 @@ pub const THREAT_PATTERNS: &[ThreatPattern] = &[ name: "indirect_command_execution", pattern: r"\$\([^)]*\$\([^)]*\)[^)]*\)|`[^`]*`[^`]*`", description: "Nested command substitution", - risk_level: RiskLevel::Medium, + risk_level: RiskLevel::Low, category: ThreatCategory::CommandInjection, }, ThreatPattern { name: "unicode_obfuscation", - pattern: r"\\u[0-9a-fA-F]{4}|\\U[0-9a-fA-F]{8}", - description: "Unicode character obfuscation", + pattern: r"(\\u[0-9a-fA-F]{4}|\\U[0-9a-fA-F]{8}){3,}", + description: "Unicode character obfuscation (3+ consecutive escapes)", risk_level: RiskLevel::Medium, category: ThreatCategory::CommandInjection, }, @@ -514,4 +514,17 @@ mod tests { assert!(!matches(pat, "rm -rf /var/log-backup")); assert!(!matches(pat, "rm -rf /var/logs")); } + + #[test] + fn unicode_obfuscation_requires_consecutive_escapes() { + let pat = "unicode_obfuscation"; + // Isolated escapes in legitimate code/strings should NOT match + assert!(!matches(pat, r"\u0041")); + assert!(!matches(pat, r"echo \u00e9 \u00e8")); + // Runs of 3+ consecutive escapes (obfuscation) should match + assert!(matches(pat, r"\u0041\u0042\u0043")); + assert!(matches(pat, r"\U00000041\U00000042\U00000043")); + // Mixed 4-digit and 8-digit forms should also match + assert!(matches(pat, r"\u0065\U00000076\u0061")); + } }