ai_agent/tools/powershell/
destructive_command_warning.rs1use once_cell::sync::Lazy;
5use regex::Regex;
6
7struct DestructivePattern {
9 pattern: Regex,
10 warning: &'static str,
11}
12
13fn get_destructive_patterns() -> Vec<DestructivePattern> {
15 vec![
16 DestructivePattern {
18 pattern: Regex::new(r"(?:^|[|;&\n({])\s*(Remove-Item|rm|del|rd|rmdir|ri)\b[^|;&\n}]*-Recurse\b[^|;&\n}]*-Force\b").unwrap(),
19 warning: "Note: may recursively force-remove files",
20 },
21 DestructivePattern {
22 pattern: Regex::new(r"(?:^|[|;&\n({])\s*(Remove-Item|rm|del|rd|rmdir|ri)\b[^|;&\n}]*-Force\b[^|;&\n}]*-Recurse\b").unwrap(),
23 warning: "Note: may recursively force-remove files",
24 },
25 DestructivePattern {
26 pattern: Regex::new(r"(?:^|[|;&\n({])\s*(Remove-Item|rm|del|rd|rmdir|ri)\b[^|;&\n}]*-Recurse\b").unwrap(),
27 warning: "Note: may recursively remove files",
28 },
29 DestructivePattern {
30 pattern: Regex::new(r"(?:^|[|;&\n({])\s*(Remove-Item|rm|del|rd|rmdir|ri)\b[^|;&\n}]*-Force\b").unwrap(),
31 warning: "Note: may force-remove files",
32 },
33 DestructivePattern {
35 pattern: Regex::new(r"\bClear-Content\b[^|;&\n]*\*").unwrap(),
36 warning: "Note: may clear content of multiple files",
37 },
38 DestructivePattern {
40 pattern: Regex::new(r"\bFormat-Volume\b").unwrap(),
41 warning: "Note: may format a disk volume",
42 },
43 DestructivePattern {
44 pattern: Regex::new(r"\bClear-Disk\b").unwrap(),
45 warning: "Note: may clear a disk",
46 },
47 DestructivePattern {
49 pattern: Regex::new(r"\bgit\s+reset\s+--hard\b").unwrap(),
50 warning: "Note: may discard uncommitted changes",
51 },
52 DestructivePattern {
53 pattern: Regex::new(r"\bgit\s+push\b[^|;&\n]*\s+(--force|--force-with-lease|-f)\b").unwrap(),
54 warning: "Note: may overwrite remote history",
55 },
56 DestructivePattern {
58 pattern: Regex::new(r"\bgit\s+stash\s+(drop|clear)\b").unwrap(),
59 warning: "Note: may permanently remove stashed changes",
60 },
61 DestructivePattern {
63 pattern: Regex::new(r"\b(DROP|TRUNCATE)\s+(TABLE|DATABASE|SCHEMA)\b").unwrap(),
64 warning: "Note: may drop or truncate database objects",
65 },
66 DestructivePattern {
68 pattern: Regex::new(r"\bStop-Computer\b").unwrap(),
69 warning: "Note: will shut down the computer",
70 },
71 DestructivePattern {
72 pattern: Regex::new(r"\bRestart-Computer\b").unwrap(),
73 warning: "Note: will restart the computer",
74 },
75 DestructivePattern {
76 pattern: Regex::new(r"\bClear-RecycleBin\b").unwrap(),
77 warning: "Note: permanently deletes recycled files",
78 },
79 ]
80}
81
82static DESTRUCTIVE_PATTERNS: Lazy<Vec<DestructivePattern>> = Lazy::new(get_destructive_patterns);
83
84pub fn get_destructive_command_warning(command: &str) -> Option<&'static str> {
87 for pattern in DESTRUCTIVE_PATTERNS.iter() {
88 if pattern.pattern.is_match(command) {
89 return Some(pattern.warning);
90 }
91 }
92
93 let lower = command.to_lowercase();
95 if lower.contains("git") && lower.contains("clean") {
96 let has_f = lower.contains(" -f ")
98 || lower.contains(" -f\n")
99 || lower.contains(" -f\t")
100 || lower.contains(" --force")
101 || lower.contains(" -fd");
102 let has_dry_run = lower.contains(" -n ") || lower.contains(" --dry-run");
103 if has_f && !has_dry_run {
104 return Some("Note: may permanently delete untracked files");
105 }
106 }
107
108 None
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn test_remove_item_recursive_force() {
117 let warning = get_destructive_command_warning("Remove-Item -Path ./foo -Recurse -Force");
118 assert!(warning.is_some());
119 }
120
121 #[test]
122 fn test_git_reset_hard() {
123 let warning = get_destructive_command_warning("git reset --hard");
124 assert!(warning.is_some());
125 }
126
127 #[test]
128 fn test_safe_command() {
129 let warning = get_destructive_command_warning("Get-ChildItem");
130 assert!(warning.is_none());
131 }
132}