Skip to main content

infigraph_core/security/
mod.rs

1mod detect;
2mod format;
3mod rules;
4
5pub use detect::*;
6pub use format::*;
7pub use rules::*;
8
9#[cfg(test)]
10mod tests {
11    use super::detect::scan_file;
12    use super::*;
13    use std::io::Write;
14
15    fn scan_str(content: &str, ext: &str) -> Vec<Finding> {
16        let dir = tempfile::tempdir().unwrap();
17        let file = dir.path().join(format!("test.{}", ext));
18        let mut f = std::fs::File::create(&file).unwrap();
19        f.write_all(content.as_bytes()).unwrap();
20        let mut stats = ScanStats::default();
21        scan_file(&file, &format!("test.{}", ext), ext, &mut stats).unwrap();
22        stats.findings
23    }
24
25    #[test]
26    fn detects_pickle_loads() {
27        let findings = scan_str("data = pickle.loads(user_input)", "py");
28        assert!(findings.iter().any(|f| f.rule_id == "SEC030"));
29    }
30
31    #[test]
32    fn detects_hardcoded_password() {
33        let findings = scan_str("password = \"s3cr3t\"", "py");
34        assert!(findings.iter().any(|f| f.rule_id == "SEC010"));
35    }
36
37    #[test]
38    fn detects_eval_js() {
39        let findings = scan_str("eval(userInput)", "js");
40        assert!(findings.iter().any(|f| f.rule_id == "SEC021"));
41    }
42
43    #[test]
44    fn detects_md5() {
45        let findings = scan_str("digest = md5(password)", "py");
46        assert!(findings.iter().any(|f| f.category == Category::WeakCrypto));
47    }
48
49    #[test]
50    fn detects_innerhtml() {
51        let findings = scan_str("el.innerHTML = userInput", "js");
52        assert!(findings.iter().any(|f| f.rule_id == "SEC100"));
53    }
54
55    #[test]
56    fn no_false_positive_yaml_safe() {
57        let findings = scan_str("data = yaml.load(f, loader=yaml.SafeLoader)", "py");
58        assert!(!findings.iter().any(|f| f.rule_id == "SEC032"));
59    }
60
61    #[test]
62    fn sanitizer_suppresses_sql_injection() {
63        let code = "query = sanitize_sql(user_input)\ncursor.execute(query)";
64        let findings = scan_str(code, "py");
65        let sql_findings: Vec<_> = findings
66            .iter()
67            .filter(|f| f.category == Category::SqlInjection)
68            .collect();
69        assert!(!sql_findings.is_empty(), "should still detect execute()");
70        assert!(
71            sql_findings.iter().all(|f| f.suppressed),
72            "should be suppressed by sanitize_sql"
73        );
74        assert!(sql_findings[0].sanitizer_hint.as_deref() == Some("sanitize_sql"));
75    }
76
77    #[test]
78    fn sanitizer_suppresses_xss_dompurify() {
79        let code = "const clean = DOMPurify.sanitize(content);\nel.innerHTML = clean;";
80        let findings = scan_str(code, "js");
81        let xss: Vec<_> = findings
82            .iter()
83            .filter(|f| f.category == Category::XssRisk)
84            .collect();
85        assert!(!xss.is_empty());
86        assert!(
87            xss.iter().all(|f| f.suppressed),
88            "innerHTML near DOMPurify should be suppressed"
89        );
90    }
91
92    #[test]
93    fn no_suppression_without_sanitizer() {
94        let code = "cursor.execute(\"SELECT * FROM users WHERE name = \" + user_input)";
95        let findings = scan_str(code, "py");
96        let sql: Vec<_> = findings
97            .iter()
98            .filter(|f| f.category == Category::SqlInjection)
99            .collect();
100        assert!(!sql.is_empty());
101        assert!(
102            sql.iter().all(|f| !f.suppressed),
103            "no sanitizer = not suppressed"
104        );
105    }
106
107    #[test]
108    fn sanitizer_suppresses_command_injection() {
109        let code = "safe_arg = shlex.quote(user_input)\nos.system(safe_arg)";
110        let findings = scan_str(code, "py");
111        let cmd: Vec<_> = findings
112            .iter()
113            .filter(|f| f.category == Category::CommandInjection)
114            .collect();
115        assert!(!cmd.is_empty());
116        assert!(
117            cmd.iter().all(|f| f.suppressed),
118            "shlex.quote nearby should suppress"
119        );
120    }
121
122    #[test]
123    fn sanitizer_suppresses_path_traversal() {
124        let code = "safe = os.path.realpath(user_path)\nopen(safe)";
125        let findings = scan_str(code, "py");
126        let path_findings: Vec<_> = findings
127            .iter()
128            .filter(|f| f.category == Category::PathTraversal)
129            .collect();
130        for f in &path_findings {
131            assert!(
132                f.suppressed,
133                "realpath nearby should suppress path traversal"
134            );
135        }
136    }
137}