safe_shell_scanner/
scanner.rs1use crate::rules::{built_in_rules, Rule};
2
3#[derive(Debug, Clone)]
4pub struct Finding {
5 pub rule_id: String,
6 pub description: String,
7 pub matched: String,
8 pub start: usize,
9 pub end: usize,
10}
11
12pub struct Scanner {
13 rules: Vec<Rule>,
14}
15
16impl Scanner {
17 pub fn new() -> Self {
18 Self {
19 rules: built_in_rules(),
20 }
21 }
22
23 pub fn scan(&self, text: &str) -> Vec<Finding> {
24 let mut findings = Vec::new();
25 for rule in &self.rules {
26 for mat in rule.pattern.find_iter(text) {
27 findings.push(Finding {
28 rule_id: rule.id.clone(),
29 description: rule.description.clone(),
30 matched: mat.as_str().to_string(),
31 start: mat.start(),
32 end: mat.end(),
33 });
34 }
35 }
36 findings
37 }
38
39 pub fn contains_secret(&self, text: &str) -> bool {
40 self.rules.iter().any(|rule| rule.pattern.is_match(text))
41 }
42}
43
44impl Default for Scanner {
45 fn default() -> Self {
46 Self::new()
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
55 fn detects_aws_access_key() {
56 let scanner = Scanner::new();
57 assert!(scanner.contains_secret("AKIAIOSFODNN7EXAMPLE"));
58 }
59
60 #[test]
61 fn detects_github_token() {
62 let scanner = Scanner::new();
63 assert!(scanner.contains_secret("ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij"));
64 }
65
66 #[test]
67 fn detects_private_key() {
68 let scanner = Scanner::new();
69 assert!(scanner.contains_secret("-----BEGIN RSA PRIVATE KEY-----"));
70 }
71
72 #[test]
73 fn scan_returns_findings() {
74 let scanner = Scanner::new();
75 let findings = scanner.scan("my key is AKIAIOSFODNN7EXAMPLE ok");
76 assert!(!findings.is_empty());
77 assert_eq!(findings[0].rule_id, "aws-access-key");
78 }
79
80 #[test]
81 fn scan_multiple_secrets() {
82 let scanner = Scanner::new();
83 let text = "aws=AKIAIOSFODNN7EXAMPLE and key=-----BEGIN RSA PRIVATE KEY-----";
84 let findings = scanner.scan(text);
85 assert!(findings.len() >= 2);
86 }
87
88 #[test]
89 fn ignores_safe_text() {
90 let scanner = Scanner::new();
91 assert!(!scanner.contains_secret("hello world"));
92 assert!(!scanner.contains_secret("npm install express"));
93 assert!(!scanner.contains_secret("PATH=/usr/bin:/usr/local/bin"));
94 }
95}