Skip to main content

gatekpr_patterns/
security.rs

1//! Security-related patterns for vulnerability detection
2//!
3//! Includes patterns for dangerous functions, hardcoded secrets, and security best practices.
4
5use crate::registry::PatternRegistry;
6use once_cell::sync::Lazy;
7
8/// Pre-built security pattern registry
9pub static SECURITY_PATTERNS: Lazy<PatternRegistry> = Lazy::new(|| {
10    let mut registry = PatternRegistry::new();
11
12    // Dangerous function patterns
13    registry.register("eval", r"(?i)\beval\s*\(").unwrap();
14
15    registry
16        .register("inner_html", r"(?i)\.innerHTML\s*=")
17        .unwrap();
18
19    registry
20        .register("dangerous_inner_html", r"(?i)dangerouslySetInnerHTML")
21        .unwrap();
22
23    registry
24        .register("exec", r"(?i)(child_process\.exec|shell_exec|exec\s*\()")
25        .unwrap();
26
27    registry
28        .register(
29            "sql_concat",
30            r#"(?i)(\+\s*['"].*SELECT|SELECT.*\+\s*['"]|['"].*\+.*WHERE)"#,
31        )
32        .unwrap();
33
34    // Hardcoded secret patterns
35    registry
36        .register("shopify_access_token", r"shpat_[a-fA-F0-9]{32}")
37        .unwrap();
38
39    registry
40        .register("shopify_secret", r"shpss_[a-fA-F0-9]{32}")
41        .unwrap();
42
43    registry
44        .register("stripe_live_key", r"sk_live_[a-zA-Z0-9]{24,}")
45        .unwrap();
46
47    registry
48        .register("stripe_test_key", r"sk_test_[a-zA-Z0-9]{24,}")
49        .unwrap();
50
51    registry
52        .register("aws_access_key", r"AKIA[0-9A-Z]{16}")
53        .unwrap();
54
55    registry
56        .register(
57            "aws_secret_key",
58            r#"(?i)aws.{0,20}['"][0-9a-zA-Z/+]{40}['"]"#,
59        )
60        .unwrap();
61
62    registry
63        .register("private_key_rsa", r"-----BEGIN RSA PRIVATE KEY-----")
64        .unwrap();
65
66    registry
67        .register("private_key_generic", r"-----BEGIN PRIVATE KEY-----")
68        .unwrap();
69
70    registry
71        .register(
72            "generic_api_key",
73            r#"(?i)(api[_-]?key|apikey)\s*[:=]\s*['"][a-zA-Z0-9_-]{20,}['"]"#,
74        )
75        .unwrap();
76
77    registry
78        .register(
79            "generic_secret",
80            r#"(?i)(secret|password|passwd|pwd)\s*[:=]\s*['"][^'"]{8,}['"]"#,
81        )
82        .unwrap();
83
84    // HTTP URLs (we filter localhost in code since regex doesn't support lookahead)
85    registry
86        .register("http_url", r#"['"]http://[^'"]+['"]"#)
87        .unwrap();
88
89    // Localhost patterns (safe HTTP URLs)
90    registry
91        .register(
92            "localhost_http",
93            r#"['"]http://(localhost|127\.0\.0\.1)[^'"]*['"]"#,
94        )
95        .unwrap();
96
97    // Command injection vectors
98    registry
99        .register(
100            "command_injection",
101            r"(?i)(os\.system|subprocess\.call|subprocess\.run|Runtime\.exec)",
102        )
103        .unwrap();
104
105    // XSS vectors
106    registry
107        .register("document_write", r"(?i)document\.write\s*\(")
108        .unwrap();
109
110    registry
111        .register("outerhtml", r"(?i)\.outerHTML\s*=")
112        .unwrap();
113
114    registry
115});
116
117/// Pattern keys for dangerous functions
118pub const DANGEROUS_FUNCTION_KEYS: &[&str] = &[
119    "eval",
120    "inner_html",
121    "dangerous_inner_html",
122    "exec",
123    "sql_concat",
124    "command_injection",
125    "document_write",
126    "outerhtml",
127];
128
129/// Pattern keys for hardcoded secrets
130pub const HARDCODED_SECRET_KEYS: &[&str] = &[
131    "shopify_access_token",
132    "shopify_secret",
133    "stripe_live_key",
134    "stripe_test_key",
135    "aws_access_key",
136    "aws_secret_key",
137    "private_key_rsa",
138    "private_key_generic",
139    "generic_api_key",
140    "generic_secret",
141];
142
143/// Pattern keys for HTTP detection (insecure = http_url - localhost_http)
144pub const HTTP_URL_KEYS: &[&str] = &["http_url", "localhost_http"];
145
146/// Find all security issues in text
147pub fn find_security_issues(text: &str) -> SecurityIssues {
148    let mut issues = SecurityIssues::default();
149
150    // Check dangerous functions
151    for key in DANGEROUS_FUNCTION_KEYS {
152        if SECURITY_PATTERNS.is_match(key, text) {
153            issues.dangerous_functions.push(key.to_string());
154        }
155    }
156
157    // Check hardcoded secrets
158    for key in HARDCODED_SECRET_KEYS {
159        let matches = SECURITY_PATTERNS.find_all(key, text);
160        if !matches.is_empty() {
161            issues.hardcoded_secrets.push(SecretMatch {
162                pattern: key.to_string(),
163                count: matches.len(),
164            });
165        }
166    }
167
168    // Check insecure HTTP (http URLs that are NOT localhost)
169    let http_matches = SECURITY_PATTERNS.find_all("http_url", text);
170    let localhost_matches = SECURITY_PATTERNS.find_all("localhost_http", text);
171
172    // Check if any HTTP URL is not localhost
173    for http_match in &http_matches {
174        let is_localhost = localhost_matches
175            .iter()
176            .any(|lm| lm.start == http_match.start && lm.end == http_match.end);
177        if !is_localhost {
178            issues.insecure_http = true;
179            break;
180        }
181    }
182
183    issues
184}
185
186/// Security issues found in code
187#[derive(Debug, Clone, Default)]
188pub struct SecurityIssues {
189    /// List of dangerous functions found
190    pub dangerous_functions: Vec<String>,
191    /// List of potential hardcoded secrets
192    pub hardcoded_secrets: Vec<SecretMatch>,
193    /// Whether insecure HTTP URLs were found
194    pub insecure_http: bool,
195}
196
197/// A match for a potential secret pattern
198#[derive(Debug, Clone)]
199pub struct SecretMatch {
200    /// The pattern key that matched
201    pub pattern: String,
202    /// Number of matches found
203    pub count: usize,
204}
205
206impl SecurityIssues {
207    /// Check if any security issues were found
208    pub fn has_issues(&self) -> bool {
209        !self.dangerous_functions.is_empty()
210            || !self.hardcoded_secrets.is_empty()
211            || self.insecure_http
212    }
213
214    /// Get total number of issues
215    pub fn total_count(&self) -> usize {
216        self.dangerous_functions.len()
217            + self
218                .hardcoded_secrets
219                .iter()
220                .map(|s| s.count)
221                .sum::<usize>()
222            + if self.insecure_http { 1 } else { 0 }
223    }
224
225    /// Get severity level (critical if secrets found)
226    pub fn severity(&self) -> &'static str {
227        if !self.hardcoded_secrets.is_empty() {
228            "critical"
229        } else if !self.dangerous_functions.is_empty() {
230            "high"
231        } else if self.insecure_http {
232            "medium"
233        } else {
234            "none"
235        }
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    #[test]
244    fn test_eval_detection() {
245        let code = "const result = eval(userInput);";
246        let issues = find_security_issues(code);
247        assert!(issues.dangerous_functions.contains(&"eval".to_string()));
248    }
249
250    #[test]
251    fn test_inner_html_detection() {
252        let code = r#"element.innerHTML = userInput;"#;
253        let issues = find_security_issues(code);
254        assert!(issues
255            .dangerous_functions
256            .contains(&"inner_html".to_string()));
257    }
258
259    #[test]
260    fn test_shopify_token_detection() {
261        let code = r#"const token = "shpat_1234567890abcdef1234567890abcdef";"#;
262        let issues = find_security_issues(code);
263        assert!(!issues.hardcoded_secrets.is_empty());
264        assert_eq!(issues.severity(), "critical");
265    }
266
267    #[test]
268    fn test_stripe_key_detection() {
269        let code = r#"const key = "sk_live_1234567890abcdefghijklmnop";"#;
270        let issues = find_security_issues(code);
271        assert!(!issues.hardcoded_secrets.is_empty());
272    }
273
274    #[test]
275    fn test_no_issues() {
276        let code = r#"
277            const data = sanitize(userInput);
278            element.textContent = data;
279        "#;
280        let issues = find_security_issues(code);
281        assert!(!issues.has_issues());
282    }
283
284    #[test]
285    fn test_insecure_http() {
286        let code = r#"fetch("http://api.example.com/data")"#;
287        let issues = find_security_issues(code);
288        assert!(issues.insecure_http);
289    }
290
291    #[test]
292    fn test_localhost_http_allowed() {
293        let code = r#"fetch("http://localhost:3000/api")"#;
294        let issues = find_security_issues(code);
295        assert!(!issues.insecure_http);
296    }
297}