Skip to main content

oxigdal_security/scanning/
secrets.rs

1//! Secret detection.
2
3use crate::scanning::{Finding, ScanResult, ScanType, Severity};
4use regex::Regex;
5
6/// Secret scanner.
7pub struct SecretScanner {
8    patterns: Vec<(String, Regex)>,
9}
10
11impl SecretScanner {
12    /// Create new secret scanner.
13    pub fn new() -> Self {
14        let mut scanner = Self {
15            patterns: Vec::new(),
16        };
17        scanner.add_default_patterns();
18        scanner
19    }
20
21    fn add_default_patterns(&mut self) {
22        // AWS Access Key
23        self.add_pattern("AWS Access Key", r"AKIA[0-9A-Z]{16}");
24
25        // API Key pattern
26        self.add_pattern(
27            "Generic API Key",
28            r#"api[_-]?key["']?\s*[:=]\s*["']?([a-zA-Z0-9_-]{32,})"#,
29        );
30
31        // Private key
32        self.add_pattern("Private Key", r"-----BEGIN (RSA |EC )?PRIVATE KEY-----");
33    }
34
35    fn add_pattern(&mut self, name: &str, pattern: &str) {
36        if let Ok(regex) = Regex::new(pattern) {
37            self.patterns.push((name.to_string(), regex));
38        }
39    }
40
41    /// Scan text for secrets.
42    pub fn scan(&self, text: &str) -> ScanResult {
43        let mut findings = Vec::new();
44
45        for (name, regex) in &self.patterns {
46            for mat in regex.find_iter(text) {
47                findings.push(Finding {
48                    id: uuid::Uuid::new_v4().to_string(),
49                    severity: Severity::Critical,
50                    description: format!("Possible {} detected", name),
51                    location: Some(format!("Position: {}", mat.start())),
52                });
53            }
54        }
55
56        ScanResult {
57            scan_type: ScanType::Secrets,
58            findings,
59            scanned_at: chrono::Utc::now(),
60        }
61    }
62}
63
64impl Default for SecretScanner {
65    fn default() -> Self {
66        Self::new()
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn test_secret_scanner() {
76        let scanner = SecretScanner::new();
77        let text = "AWS Key: AKIAIOSFODNN7EXAMPLE";
78        let result = scanner.scan(text);
79
80        assert!(!result.findings.is_empty());
81        assert_eq!(result.scan_type, ScanType::Secrets);
82    }
83}