security_rust/file/
data_leak.rs1use regex::Regex;
4use std::sync::LazyLock;
5
6use crate::{AttackCategory, DetectionResult, Detector, Severity};
7
8static CC_PAN: LazyLock<Regex> = LazyLock::new(|| {
9 Regex::new(r"\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12}|(?:2131|1800|35\d{3})\d{11})\b").unwrap()
10});
11
12static PATTERNS: LazyLock<Vec<Regex>> = LazyLock::new(|| {
13 vec![
14 Regex::new(r"AKIA[0-9A-Z]{16}").unwrap(),
15 Regex::new(r"-----BEGIN\s*(?:RSA\s*)?PRIVATE\s*KEY").unwrap(),
16 Regex::new(r"-----BEGIN\s*CERTIFICATE").unwrap(),
17 Regex::new(r"-----BEGIN\s*DSA\s*PRIVATE").unwrap(),
18 Regex::new(r"-----BEGIN\s*EC\s*PRIVATE").unwrap(),
19 Regex::new(r"-----BEGIN\s*PGP\s*PRIVATE").unwrap(),
20 Regex::new(r"sk-[A-Za-z0-9]{32,}").unwrap(),
21 Regex::new(r"(?i)mongodb(?:\+srv)?://[^/\s]+").unwrap(),
22 Regex::new(r"(?i)mysql://[^/\s]+").unwrap(),
23 Regex::new(r"(?i)postgres(?:ql)?://[^/\s]+").unwrap(),
24 Regex::new(r"(?i)redis://[^/\s]+").unwrap(),
25 Regex::new(r"(?i)jdbc:[a-z]+://").unwrap(),
26 ]
27});
28
29fn luhn_valid(pan: &str) -> bool {
30 let digits: Vec<u8> = pan
31 .as_bytes()
32 .iter()
33 .filter_map(|b| {
34 if b.is_ascii_digit() {
35 Some(b - b'0')
36 } else {
37 None
38 }
39 })
40 .collect();
41 if digits.len() < 13 {
42 return false;
43 }
44 let sum: u32 = digits
45 .iter()
46 .rev()
47 .enumerate()
48 .map(|(i, &d)| {
49 if i % 2 == 1 {
50 let doubled = d as u32 * 2;
51 if doubled > 9 { doubled - 9 } else { doubled }
52 } else {
53 d as u32
54 }
55 })
56 .sum();
57 sum.is_multiple_of(10)
58}
59
60pub struct DataLeakDetector;
61
62impl Detector for DataLeakDetector {
63 fn name(&self) -> &'static str {
64 "data_leak"
65 }
66
67 fn detect(&self, input: &str) -> Option<DetectionResult> {
68 if let Some(m) = CC_PAN.find(input)
69 && luhn_valid(m.as_str())
70 {
71 return Some(DetectionResult {
72 attack_type: "data_leak".into(),
73 category: AttackCategory::File,
74 severity: Severity::Critical,
75 matched_pattern: m.as_str().to_string(),
76 offset: m.start(),
77 message: "Sensitive data leak detected (credit card)".into(),
78 });
79 }
80 for re in PATTERNS.iter() {
81 if let Some(m) = re.find(input) {
82 return Some(DetectionResult {
83 attack_type: "data_leak".into(),
84 category: AttackCategory::File,
85 severity: Severity::Critical,
86 matched_pattern: m.as_str().to_string(),
87 offset: m.start(),
88 message: "Sensitive data leak detected".into(),
89 });
90 }
91 }
92 None
93 }
94}