Skip to main content

dataprof_db/security/
utils.rs

1//! Security utility functions
2
3/// Sanitize error messages to prevent information disclosure
4pub fn sanitize_error_message(error_message: &str) -> String {
5    let mut sanitized = error_message.to_string();
6
7    let patterns_to_remove = [
8        r"postgresql://[^\s]+",
9        r"mysql://[^\s]+",
10        r"sqlite://[^\s]+",
11        r"password[=:]\s*[^\s;]+",
12        r"pass[=:]\s*[^\s;]+",
13        r"pwd[=:]\s*[^\s;]+",
14        r"Password [^:]*: ['\x22][^'\x22]+['\x22]",
15        r"verification failed: ['\x22][^'\x22]+['\x22]",
16        r"[A-Za-z]:\\[^\s]+",
17        r"/[^\s]*/.env[^\s]*",
18        r"/[^\s]*/config[^\s]*",
19        r"/home/[^\s/]+/[^\s]*",
20        r"/etc/[^\s]*",
21        r"\.[^\s]*conf[^\s]*",
22        r"\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b",
23        r"user[\s=:]['\x22]?[^'\x22\s;]+['\x22]?",
24        r"username[\s=:]['\x22]?[^'\x22\s;]+['\x22]?",
25        r"for user: [a-zA-Z0-9_]+",
26        r"failed for user: [a-zA-Z0-9_]+",
27        r"for username: ['\x22][^'\x22]+['\x22]",
28        r"denied for username: ['\x22][^'\x22]+['\x22]",
29        r"permission denied: ['\x22][^'\x22]+['\x22]",
30        r"User [^:]*: ['\x22][^'\x22]+['\x22]",
31    ];
32
33    for pattern in &patterns_to_remove {
34        if let Ok(regex) = regex::Regex::new(pattern) {
35            sanitized = regex.replace_all(&sanitized, "[REDACTED]").to_string();
36        }
37    }
38
39    if sanitized.len() > 500 {
40        sanitized = format!("{}... [truncated for security]", &sanitized[..500]);
41    }
42
43    sanitized
44}
45
46/// Add query parameter to connection string
47pub fn add_query_param(connection_string: String, key: &str, value: &str) -> String {
48    if connection_string.contains('?') {
49        format!("{}&{}={}", connection_string, key, value)
50    } else {
51        format!("{}?{}={}", connection_string, key, value)
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn test_add_query_param() {
61        let base = "postgresql://user@host/db".to_string();
62        let result = add_query_param(base, "sslmode", "require");
63        assert_eq!(result, "postgresql://user@host/db?sslmode=require");
64
65        let with_existing = "postgresql://user@host/db?existing=value".to_string();
66        let result = add_query_param(with_existing, "sslmode", "require");
67        assert_eq!(
68            result,
69            "postgresql://user@host/db?existing=value&sslmode=require"
70        );
71    }
72
73    #[test]
74    fn test_error_message_sanitization() {
75        let error_with_password =
76            "Connection failed: postgresql://user:secret123@localhost:5432/db";
77        let sanitized = sanitize_error_message(error_with_password);
78        assert!(!sanitized.contains("secret123"));
79        assert!(sanitized.contains("[REDACTED]"));
80
81        let error_with_path = "Config file not found: C:\\Users\\admin\\.env";
82        let sanitized = sanitize_error_message(error_with_path);
83        assert!(sanitized.contains("[REDACTED]"));
84
85        let normal_error = "Table 'products' doesn't exist";
86        let sanitized = sanitize_error_message(normal_error);
87        assert_eq!(sanitized, normal_error);
88    }
89}