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 Regex::new(r"(?i)eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+").unwrap(),
27 ]
28});
29
30fn luhn_valid(pan: &str) -> bool {
31 let digits: Vec<u8> = pan
32 .as_bytes()
33 .iter()
34 .filter_map(|b| {
35 if b.is_ascii_digit() {
36 Some(b - b'0')
37 } else {
38 None
39 }
40 })
41 .collect();
42 if digits.len() < 13 {
43 return false;
44 }
45 let sum: u32 = digits
46 .iter()
47 .rev()
48 .enumerate()
49 .map(|(i, &d)| {
50 if i % 2 == 1 {
51 let doubled = d as u32 * 2;
52 if doubled > 9 { doubled - 9 } else { doubled }
53 } else {
54 d as u32
55 }
56 })
57 .sum();
58 sum.is_multiple_of(10)
59}
60
61pub struct DataLeakDetector;
62
63impl Detector for DataLeakDetector {
64 fn name(&self) -> &'static str {
65 "data_leak"
66 }
67
68 fn detect(&self, input: &str) -> Option<DetectionResult> {
69 if let Some(m) = CC_PAN.find(input)
70 && luhn_valid(m.as_str())
71 {
72 return Some(DetectionResult {
73 attack_type: "data_leak".into(),
74 category: AttackCategory::File,
75 severity: Severity::Critical,
76 matched_pattern: m.as_str().to_string(),
77 offset: m.start(),
78 message: "Sensitive data leak detected (credit card)".into(),
79 });
80 }
81 for re in PATTERNS.iter() {
82 if let Some(m) = re.find(input) {
83 return Some(DetectionResult {
84 attack_type: "data_leak".into(),
85 category: AttackCategory::File,
86 severity: Severity::Critical,
87 matched_pattern: m.as_str().to_string(),
88 offset: m.start(),
89 message: "Sensitive data leak detected".into(),
90 });
91 }
92 }
93 None
94 }
95}