Skip to main content

kora_lib/
sanitize.rs

1//! Security-focused logging and error message sanitization
2//!
3//! This module provides utilities to automatically redact sensitive information
4//! from error messages and logs, including:
5//! - URLs with embedded credentials (any protocol: redis://, postgres://, http://, etc.)
6//! - Long hex strings (potential private keys)
7
8use regex::Regex;
9use std::sync::LazyLock;
10
11/// Regex patterns for detecting sensitive data
12static URL_WITH_CREDENTIALS_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
13    // Generic URL pattern with embedded credentials: protocol://user:password@host
14    // Matches any protocol (redis, http, https, postgres, mysql, mongodb, etc.)
15    Regex::new(r"[a-z][a-z0-9+.-]*://[^:@\s]+:[^@\s]+@[^\s]+")
16        .expect("Failed to create url regex pattern")
17});
18
19static HEX_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
20    // Long hex strings (likely keys/hashes) - 32+ chars, with optional 0x prefix
21    Regex::new(r"(?:0x)?[0-9a-fA-F]{32,}").expect("Failed to create hex regex pattern")
22});
23
24/// Sanitizes a message by redacting sensitive information
25pub fn sanitize_message(message: &str) -> String {
26    let mut result = message.to_string();
27
28    result = URL_WITH_CREDENTIALS_PATTERN.replace_all(&result, "[REDACTED_URL]").to_string();
29
30    result = HEX_PATTERN.replace_all(&result, "[REDACTED_HEX]").to_string();
31
32    result
33}
34
35/// Sanitizes an error message based on the `unsafe-debug` feature flag
36///
37/// - With `unsafe-debug`: Returns the original error message
38/// - Without `unsafe-debug`: Returns a sanitized version with sensitive data redacted
39#[macro_export]
40macro_rules! sanitize_error {
41    ($error:expr) => {{
42        #[cfg(feature = "unsafe-debug")]
43        {
44            format!("{}", $error)
45        }
46        #[cfg(not(feature = "unsafe-debug"))]
47        {
48            $crate::sanitize::sanitize_message(&format!("{}", $error))
49        }
50    }};
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn test_sanitize_url_with_credentials_redis() {
59        let msg = "Failed to connect to redis://user:password@localhost:6379";
60        let sanitized = sanitize_message(msg);
61        assert!(sanitized.contains("[REDACTED_URL]"));
62        assert!(!sanitized.contains("password"));
63        assert!(!sanitized.contains("redis://user:"));
64        // Ensure the error message context remains
65        assert!(sanitized.contains("Failed to connect to"));
66    }
67
68    #[test]
69    fn test_sanitize_url_with_credentials_http() {
70        let msg = "Request failed: https://user:token@api.example.com/endpoint";
71        let sanitized = sanitize_message(msg);
72        assert!(sanitized.contains("[REDACTED_URL]"));
73        assert!(!sanitized.contains("token"));
74        assert!(!sanitized.contains("https://user:"));
75    }
76
77    #[test]
78    fn test_sanitize_url_with_credentials_postgres() {
79        let msg = "DB error: postgres://admin:secret123@db.internal:5432/mydb";
80        let sanitized = sanitize_message(msg);
81        assert!(sanitized.contains("[REDACTED_URL]"));
82        assert!(!sanitized.contains("admin"));
83        assert!(!sanitized.contains("secret123"));
84    }
85
86    #[test]
87    fn test_sanitize_hex_string() {
88        let msg = "Key: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
89        let sanitized = sanitize_message(msg);
90        assert!(sanitized.contains("[REDACTED_HEX]"));
91    }
92}