security_rust/data/
csv_injection.rs1use crate::{regex_detect, AttackCategory, DetectionResult, Detector, Severity};
4use regex::Regex;
5use std::sync::LazyLock;
6
7static PATTERNS: LazyLock<Vec<Regex>> = LazyLock::new(|| {
8 vec![
9 Regex::new(r"(?m)^[=+\-@\t\r]").unwrap(),
10 Regex::new(r"(?im)^\s*DDE").unwrap(),
11 Regex::new(r"(?im)^\s*cmd\s*\|").unwrap(),
12 Regex::new(r"(?im)^\s*@SUM\s*\(").unwrap(),
13 ]
14});
15
16pub struct CsvInjectionDetector;
17
18impl Detector for CsvInjectionDetector {
19 fn name(&self) -> &'static str {
20 "csv_injection"
21 }
22
23 fn detect(&self, input: &str) -> Option<DetectionResult> {
24 regex_detect(&PATTERNS, self.name(), AttackCategory::Data, Severity::Medium, "CSV formula injection detected", input)
25 }
26}
27
28#[cfg(test)]
29mod tests {
30 use super::*;
31
32 #[test]
33 fn name_returns_attack_type() {
34 assert_eq!(CsvInjectionDetector.name(), "csv_injection");
35 }
36
37 #[test]
38 fn detects_formula_prefixes() {
39 for payload in [
40 "=cmd|' /C calc'!A0",
41 "+1+1",
42 "-2+3",
43 "@SUM(1+1)*cmd",
44 "\t=1",
45 "DDE;cmd",
46 "cmd|' /C calc'!A0",
47 ] {
48 let r = CsvInjectionDetector
49 .detect(payload)
50 .unwrap_or_else(|| panic!("expected detection for {:?}", payload));
51 assert_eq!(r.attack_type, "csv_injection");
52 assert_eq!(r.category, AttackCategory::Data);
53 assert_eq!(r.severity, Severity::Medium);
54 assert!(
55 !r.matched_pattern.is_empty(),
56 "matched_pattern empty for {:?}",
57 payload
58 );
59 assert!(
60 r.offset <= payload.len(),
61 "offset out of range for {:?}",
62 payload
63 );
64 }
65 }
66
67 #[test]
68 fn ignores_benign_inputs() {
69 for input in [
70 "Hello, this is a normal text input.",
71 "a=1+1",
72 "SUM(1+1)",
73 "cmd /C calc",
74 "not a formula",
75 ] {
76 assert!(
77 CsvInjectionDetector.detect(input).is_none(),
78 "false positive: {:?}",
79 input
80 );
81 }
82 }
83
84 #[test]
85 fn edge_cases() {
86 assert!(CsvInjectionDetector.detect("").is_none());
87 assert!(CsvInjectionDetector.detect(" ").is_none());
88 assert!(CsvInjectionDetector.detect("=cmd|' /C calc'!A0").is_none()); }
90}