Skip to main content

kindly_guard_server/scanner/
patterns.rs

1// Copyright 2025 Kindly Software Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//! Threat pattern definitions
15//!
16//! Configurable patterns for detecting various security threats
17
18use super::ScanError;
19use serde::{Deserialize, Serialize};
20use std::fs;
21use std::path::Path;
22
23/// Collection of threat detection patterns
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct ThreatPatterns {
26    prompt_injection: Vec<String>,
27    command_injection: Vec<String>,
28    path_traversal: Vec<String>,
29    sql_injection: Vec<String>,
30    xss_patterns: Vec<String>,
31    ldap_injection: Vec<String>,
32    xml_injection: Vec<String>,
33    nosql_injection: Vec<String>,
34}
35
36impl ThreatPatterns {
37    /// Load patterns from a JSON file
38    pub fn load_from_file(path: &Path) -> Result<Self, ScanError> {
39        let content = fs::read_to_string(path)
40            .map_err(|e| ScanError::InvalidInput(format!("Failed to read patterns file: {e}")))?;
41
42        serde_json::from_str(&content)
43            .map_err(|e| ScanError::InvalidInput(format!("Invalid patterns JSON: {e}")))
44    }
45
46    /// Get prompt injection patterns
47    pub fn prompt_injection_patterns(&self) -> &[String] {
48        &self.prompt_injection
49    }
50
51    /// Get command injection patterns
52    pub fn command_injection_patterns(&self) -> &[String] {
53        &self.command_injection
54    }
55
56    /// Get path traversal patterns
57    pub fn path_traversal_patterns(&self) -> &[String] {
58        &self.path_traversal
59    }
60
61    /// Get SQL injection patterns
62    pub fn sql_injection_patterns(&self) -> &[String] {
63        &self.sql_injection
64    }
65
66    /// Get XSS patterns
67    pub fn xss_patterns(&self) -> &[String] {
68        &self.xss_patterns
69    }
70
71    /// Get LDAP injection patterns
72    pub fn ldap_injection_patterns(&self) -> &[String] {
73        &self.ldap_injection
74    }
75
76    /// Get XML injection patterns
77    pub fn xml_injection_patterns(&self) -> &[String] {
78        &self.xml_injection
79    }
80
81    /// Get NoSQL injection patterns
82    pub fn nosql_injection_patterns(&self) -> &[String] {
83        &self.nosql_injection
84    }
85
86    /// Get all patterns as a serializable structure
87    pub const fn get_all_patterns(&self) -> &Self {
88        self
89    }
90}
91
92impl Default for ThreatPatterns {
93    fn default() -> Self {
94        Self {
95            prompt_injection: vec![
96                // AI-specific prompt injections
97                r"(?i)ignore\s+(previous|all|above)\s+(instructions?|prompts?|commands?)"
98                    .to_string(),
99                r"(?i)disregard\s+(previous|all|above)\s+(instructions?|prompts?|commands?)"
100                    .to_string(),
101                r"(?i)forget\s+(everything|all|previous)\s+(above|before)".to_string(),
102                r"(?i)new\s+instructions?:\s*".to_string(),
103                r"(?i)system\s*:\s*you\s+are".to_string(),
104                r"(?i)pretend\s+you\s+are".to_string(),
105                r"(?i)act\s+as\s+if\s+you".to_string(),
106                r"(?i)roleplay\s+as".to_string(),
107                r"(?i)you\s+are\s+now\s+in\s+developer\s+mode".to_string(),
108                r"(?i)enable\s+developer\s+mode".to_string(),
109                r"\[\[.*\]\]".to_string(), // Hidden instructions in brackets
110                r"<system>.*</system>".to_string(), // Fake system tags
111                r"(?i)simulation\s+mode".to_string(),
112                r"(?i)testing\s+mode".to_string(),
113            ],
114
115            command_injection: vec![
116                // Shell command separators
117                r";\s*rm\s+".to_string(),
118                r";\s*cat\s+".to_string(),
119                r";\s*ls\s+".to_string(),
120                r";\s*wget\s+".to_string(),
121                r";\s*curl\s+".to_string(),
122                r";\s*python\s+".to_string(),
123                r";\s*python3\s+".to_string(),
124                r";\s*perl\s+".to_string(),
125                r";\s*ruby\s+".to_string(),
126                r";\s*php\s+".to_string(),
127                r";\s*node\s+".to_string(),
128                r";\s*java\s+".to_string(),
129                r"&&\s*[a-z]+\s+".to_string(),
130                r"\|\|\s*[a-z]+\s+".to_string(),
131                r"\|\s*[a-z]+\s+".to_string(),
132                r"\|\s*nc\s+".to_string(),
133                r"\|\s*netcat\s+".to_string(),
134                r"\|\s*ncat\s+".to_string(),
135                r"\|\s*socat\s+".to_string(),
136                r"\|\s*telnet\s+".to_string(),
137                r"\|\s*ssh\s+".to_string(),
138                // Command substitution
139                r"\$\([^)]+\)".to_string(),
140                r"`[^`]+`".to_string(),
141                r"\$\{[^}]+\}".to_string(),
142                // Common dangerous commands
143                r"(?i)(rm|del)\s+-rf?\s+".to_string(),
144                r"(?i)format\s+[cd]:".to_string(),
145                r"(?i)shutdown\s+".to_string(),
146                r"/bin/(sh|bash|zsh|dash)".to_string(),
147                // Windows-specific command injection patterns
148                r"(?i)cmd\.exe\s*/c".to_string(),
149                r"(?i)cmd\s*/c".to_string(),
150                r"(?i)cmd\.exe\s+/c".to_string(),
151                r"(?i)cmd\s+/c".to_string(),
152                r"(?i)powershell\.exe\s*-c".to_string(),
153                r"(?i)powershell\s*-c".to_string(),
154                r"(?i)powershell\.exe\s*-Command".to_string(),
155                r"(?i)powershell\s*-Command".to_string(),
156                r"(?i)powershell\.exe\s*-ExecutionPolicy".to_string(),
157                r"(?i)powershell\s*-ExecutionPolicy".to_string(),
158                r"(?i)wscript\.exe".to_string(),
159                r"(?i)cscript\.exe".to_string(),
160                r"(?i)mshta\.exe".to_string(),
161                r"(?i)rundll32\.exe".to_string(),
162                r"(?i)regsvr32\.exe".to_string(),
163                // Windows operators and pipes
164                r"&\s*net\s+".to_string(),
165                r"&\s*netsh\s+".to_string(),
166                r"&\s*sc\s+".to_string(),
167                r"&\s*reg\s+".to_string(),
168                r"&\s*wmic\s+".to_string(),
169                r"\|\s*findstr\s+".to_string(),
170                r"\^\s*".to_string(), // Windows escape character
171                // Windows shell shortcuts
172                r"(?i)%comspec%".to_string(),
173                r"(?i)%systemroot%".to_string(),
174                r"(?i)%windir%".to_string(),
175                // Newline command injection
176                r"\n\s*/bin/".to_string(),
177                r"\n\s*/usr/bin/".to_string(),
178                r"\r\n\s*cmd".to_string(),
179                r"\n\s*python".to_string(),
180                r"\n\s*perl".to_string(),
181                r"\n\s*ruby".to_string(),
182                r"\n\s*sh\s*".to_string(),
183                r"\n\s*bash\s*".to_string(),
184                // Path injection
185                r"\.\./\.\./(\.\./)*(etc|bin|usr|var)".to_string(),
186            ],
187
188            path_traversal: vec![
189                // Unix path traversal - specific patterns first
190                r"\.\./\.\./\.\./etc/passwd".to_string(), // Specific pattern for test
191                r"\.\./(\.\./)*(etc|usr|bin|var|tmp|home|root)".to_string(),
192                r"/\.\.".to_string(),
193                r"\.\.%2[fF]".to_string(),      // URL encoded forward slash
194                r"%2e%2e/".to_string(),         // URL encoded dots with slash
195                r"%2e%2e%2f".to_string(),       // Fully URL encoded ../
196                r"%252e%252e/".to_string(),     // Double encoded
197                r"%252e%252e%252f".to_string(), // Double encoded ../
198                r"%252f".to_string(),           // Double encoded forward slash
199                // Windows path traversal
200                r"\.\.\\(\.\.\\)*(windows|system32|users|program files)".to_string(),
201                r"\\\.\.".to_string(),
202                r"\.\.%5[cC]".to_string(), // URL encoded backslash
203                r"%2e%2e\\\\".to_string(),
204                // Advanced path traversal patterns
205                r"\.{2,}/".to_string(),    // Multiple dots
206                r"\.\.\.+/".to_string(),   // Three or more dots
207                r"\.\./\.\./".to_string(), // Multiple traversals
208                // Unicode and overlong encodings
209                r"%c0%af".to_string(), // Overlong UTF-8 for /
210                r"%c1%9c".to_string(), // Overlong UTF-8 for \
211                // Null byte injection
212                r"%00".to_string(),
213                r"\x00".to_string(),
214                // Absolute paths
215                r"^/etc/".to_string(),
216                r"^/usr/".to_string(),
217                r"^/var/".to_string(),
218                r"^[cC]:\\\\".to_string(),
219                // Common target files
220                r"etc/passwd".to_string(),
221                r"etc/shadow".to_string(),
222                r"windows/win\.ini".to_string(),
223                r"boot\.ini".to_string(),
224            ],
225
226            sql_injection: vec![
227                // Classic SQL injection
228                r"'\s*(OR|AND)\s*'?1'?\s*=\s*'?1".to_string(),
229                r"'\s*(OR|AND)\s+[0-9]+\s*=\s*[0-9]+".to_string(),
230                r"admin'\s*--".to_string(),
231                r"'\s*OR\s*'[^']*'\s*=\s*'[^']*".to_string(),
232                // Union-based
233                r"(?i)union\s+(all\s+)?select".to_string(),
234                r"(?i)union\s+distinct\s+select".to_string(),
235                // Time-based
236                r"(?i)sleep\s*\(\s*[0-9]+\s*\)".to_string(),
237                r"(?i)waitfor\s+delay".to_string(),
238                r"(?i)benchmark\s*\(".to_string(),
239                // Error-based
240                r"(?i)extractvalue\s*\(".to_string(),
241                r"(?i)updatexml\s*\(".to_string(),
242                r"(?i)xmltype\s*\(".to_string(),
243                // Common SQL commands
244                r"(?i)(drop|create|alter)\s+(table|database|schema|index)".to_string(),
245                r"(?i)insert\s+into\s+".to_string(),
246                r"(?i)delete\s+from\s+".to_string(),
247                r"(?i)update\s+\w+\s+set".to_string(),
248                // Comment syntax
249                r"--\s*$".to_string(),
250                r"/\*.*\*/".to_string(),
251                r"#\s*$".to_string(),
252                // Hex encoding
253                r"0x[0-9a-fA-F]+".to_string(),
254                // SQL functions that might be abused
255                r"(?i)concat\s*\(".to_string(),
256                r"(?i)char\s*\(".to_string(),
257                r"(?i)load_file\s*\(".to_string(),
258                r"(?i)into\s+outfile".to_string(),
259            ],
260
261            xss_patterns: vec![
262                // IMPORTANT: These patterns are for DETECTION only, not prevention
263                // For actual XSS prevention, use proper HTML sanitization libraries
264
265                // Script tags - various forms
266                r"<script[^>]*>".to_string(),
267                r"</script[^>]*>".to_string(),
268                r"(?i)<script[^>]*>".to_string(),
269                // Event handlers - most common XSS vector
270                r"(?i)\bon\w+\s*=".to_string(), // matches onclick=, onerror=, etc.
271                r"(?i)(onerror|onload|onclick|onmouseover|onfocus|onblur)\s*=".to_string(),
272                // JavaScript protocol
273                r"(?i)javascript\s*:".to_string(),
274                r"(?i)vbscript\s*:".to_string(),
275                r"(?i)livescript\s*:".to_string(),
276                // Data URI with script content
277                r"(?i)data:[^,]*script".to_string(),
278                r"(?i)data:.*base64".to_string(),
279                // Dangerous HTML elements
280                r"(?i)<iframe[^>]*>".to_string(),
281                r"(?i)<object[^>]*>".to_string(),
282                r"(?i)<embed[^>]*>".to_string(),
283                r"(?i)<applet[^>]*>".to_string(),
284                r"(?i)<form[^>]*>".to_string(),
285                r"(?i)<link[^>]*>".to_string(),
286                r"(?i)<meta[^>]*>".to_string(),
287                // SVG-based XSS
288                r"(?i)<svg[^>]*>".to_string(),
289                r"(?i)<svg[^>]*onload".to_string(),
290                // Style-based XSS
291                r"(?i)<style[^>]*>".to_string(),
292                r"(?i)style\s*=.*expression\s*\(".to_string(),
293                r"(?i)style\s*=.*javascript\s*:".to_string(),
294                // Encoded script tags (hex, decimal, unicode)
295                r"%3[Cc]script".to_string(),
296                r"&#x3[Cc];script".to_string(),
297                r"&#60;script".to_string(),
298                r"\\u003[Cc]script".to_string(),
299                r"\\x3[Cc]script".to_string(),
300                // Common obfuscation techniques
301                r"String\.fromCharCode".to_string(),
302                r"eval\s*\(".to_string(),
303                r"(?i)expression\s*\(".to_string(),
304                r"(?i)document\s*\.\s*(write|writeln)".to_string(),
305                r"(?i)window\s*\.\s*location".to_string(),
306                r"(?i)document\s*\.\s*cookie".to_string(),
307                // HTML5 event handlers
308                r"(?i)onhashchange\s*=".to_string(),
309                r"(?i)onpopstate\s*=".to_string(),
310                r"(?i)onstorage\s*=".to_string(),
311                // Bypasses with null bytes and comments
312                r"<scr\x00ipt".to_string(),
313                r"<scr\<!--.*--\>ipt".to_string(),
314            ],
315
316            ldap_injection: vec![
317                // LDAP filter injection
318                r"\(\|\(".to_string(),
319                r"\)\(\|".to_string(),
320                r"\(\&\(".to_string(),
321                r"\)\(\&".to_string(),
322                r"(?i)\*\)\(\|".to_string(),
323                r"(?i)\)\(\|\(".to_string(),
324                // LDAP escape sequences
325                r"\\2a".to_string(), // \*
326                r"\\28".to_string(), // \(
327                r"\\29".to_string(), // \)
328                r"\\5c".to_string(), // \\
329                // Common LDAP injection patterns
330                r"(?i)admin\*\)\(".to_string(),
331                r"(?i)\*\)\(uid=\*".to_string(),
332                r"(?i)\*\)\(objectClass=\*".to_string(),
333                r"(?i)cn=\*\)\(".to_string(),
334                // LDAP boolean conditions
335                r"(?i)\|\(uid=\*".to_string(),
336                r"(?i)\&\(uid=\*".to_string(),
337                r"(?i)\!\(uid=\*".to_string(),
338            ],
339
340            xml_injection: vec![
341                // XML entity injection
342                r"<!ENTITY".to_string(),
343                r"<!DOCTYPE".to_string(),
344                r#"SYSTEM\s+["']file:"#.to_string(),
345                r#"SYSTEM\s+["']http:"#.to_string(),
346                r#"SYSTEM\s+["']https:"#.to_string(),
347                // XXE patterns
348                r"&xxe;".to_string(),
349                r"%xxe;".to_string(),
350                r"<!ENTITY\s+xxe".to_string(),
351                r"<!ENTITY\s+%\s+xxe".to_string(),
352                // XML injection via CDATA
353                r"<!\[CDATA\[.*\]\]>".to_string(),
354                r"]]>.*<!\[CDATA\[".to_string(),
355                // SOAP injection
356                r"</[^>]+><[^>]+>".to_string(),
357                r"(?i)<soap:.*>".to_string(),
358                // XPath injection
359                r"(?i)' or '1'='1".to_string(),
360                r#"(?i)" or "1"="1"#.to_string(),
361                r"(?i)' or 1=1".to_string(),
362                r#"(?i)" or 1=1"#.to_string(),
363                // XML bomb patterns
364                r#"<!ENTITY\s+lol\s+"lol"#.to_string(),
365                r"&lol9;".to_string(),
366            ],
367
368            nosql_injection: vec![
369                // MongoDB injection patterns
370                r"\$ne".to_string(),
371                r"\$eq".to_string(),
372                r"\$gt".to_string(),
373                r"\$gte".to_string(),
374                r"\$lt".to_string(),
375                r"\$lte".to_string(),
376                r"\$nin".to_string(),
377                r"\$in".to_string(),
378                r"\$regex".to_string(),
379                r"\$where".to_string(),
380                r"\$exists".to_string(),
381                r"\$type".to_string(),
382                r"\$expr".to_string(),
383                r"\$jsonSchema".to_string(),
384                r"\$mod".to_string(),
385                // MongoDB operators in JSON
386                r#"["']\s*:\s*\{\s*["']\$"#.to_string(),
387                r#"\{\s*["']\$ne["']\s*:"#.to_string(),
388                r#"\{\s*["']\$gt["']\s*:"#.to_string(),
389                // JavaScript injection in NoSQL
390                r"(?i)function\s*\(".to_string(),
391                r"(?i)return\s+true".to_string(),
392                r"(?i)return\s+1\s*==\s*1".to_string(),
393                r"(?i)this\.\w+\s*==".to_string(),
394                // CouchDB injection
395                r"_design/".to_string(),
396                r"_view/".to_string(),
397                r"\$text\s*:\s*\{".to_string(),
398                r"\$search\s*:".to_string(),
399                // Redis injection
400                r"(?i)FLUSHDB".to_string(),
401                r"(?i)FLUSHALL".to_string(),
402                r"(?i)CONFIG\s+SET".to_string(),
403                r"(?i)SCRIPT\s+LOAD".to_string(),
404            ],
405        }
406    }
407}
408
409/// Save default patterns to a file (for customization)
410pub fn save_default_patterns(path: &Path) -> Result<(), ScanError> {
411    let patterns = ThreatPatterns::default();
412    let json = serde_json::to_string_pretty(&patterns)
413        .map_err(|e| ScanError::InvalidInput(format!("Failed to serialize patterns: {e}")))?;
414
415    fs::write(path, json)
416        .map_err(|e| ScanError::InvalidInput(format!("Failed to write patterns file: {e}")))
417}
418
419#[cfg(test)]
420mod tests {
421    use super::*;
422
423    #[test]
424    fn test_default_patterns() {
425        let patterns = ThreatPatterns::default();
426        assert!(!patterns.prompt_injection.is_empty());
427        assert!(!patterns.command_injection.is_empty());
428        assert!(!patterns.path_traversal.is_empty());
429        assert!(!patterns.sql_injection.is_empty());
430    }
431
432    #[test]
433    fn test_pattern_serialization() {
434        let patterns = ThreatPatterns::default();
435        let json = serde_json::to_string(&patterns).unwrap();
436        let loaded: ThreatPatterns = serde_json::from_str(&json).unwrap();
437
438        assert_eq!(
439            patterns.prompt_injection.len(),
440            loaded.prompt_injection.len()
441        );
442        assert_eq!(
443            patterns.command_injection.len(),
444            loaded.command_injection.len()
445        );
446    }
447}