http_tunnel_handler/
error_handling.rs

1//! Error handling and sanitization
2//!
3//! This module provides utilities for sanitizing error messages to prevent
4//! information disclosure to clients while logging full details internally.
5
6use tracing::error;
7
8/// Sanitize error messages for client responses
9///
10/// Logs the full error internally but returns a generic message to the client
11/// to prevent information disclosure of internal implementation details.
12pub fn sanitize_error(e: &anyhow::Error) -> String {
13    // Log full error internally with context
14    error!("Internal error: {:#}", e);
15
16    // Return generic message to client
17    "Internal server error".to_string()
18}
19
20/// Sanitize error with a custom client message
21///
22/// Logs the full error internally but returns a custom generic message
23pub fn sanitize_error_with_message(e: &anyhow::Error, client_message: &str) -> String {
24    // Log full error internally
25    error!("Error ({}): {:#}", client_message, e);
26
27    // Return custom message to client
28    client_message.to_string()
29}
30
31/// Check if an error should be shown to the client (for known safe errors)
32///
33/// Some errors are safe to show (like validation errors), while others
34/// should be sanitized (like database errors)
35pub fn is_safe_error(e: &anyhow::Error) -> bool {
36    let error_str = format!("{}", e);
37    let error_debug = format!("{:?}", e);
38
39    // Safe error patterns that don't leak internal details
40    error_str.contains("Invalid tunnel ID")
41        || error_str.contains("Invalid request ID")
42        || error_str.contains("Invalid connection ID")
43        || error_str.contains("Path too long")
44        || error_str.contains("Header value too long")
45        || error_str.contains("Request timeout")
46        || error_str.contains("Missing tunnel ID")
47        || error_str.contains("Request entity too large")
48        || error_debug.contains("ValidationError")
49}
50
51/// Get a user-friendly error message
52///
53/// Returns the actual error message if it's safe, otherwise returns a sanitized version
54pub fn get_client_error_message(e: &anyhow::Error) -> String {
55    if is_safe_error(e) {
56        // Log but also return to client
57        error!("Client error: {}", e);
58        format!("{}", e)
59    } else {
60        // Sanitize sensitive errors
61        sanitize_error(e)
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use anyhow::anyhow;
69
70    #[test]
71    fn test_sanitize_error_hides_details() {
72        let err = anyhow!("Failed to connect to DynamoDB at 10.0.1.5:8000");
73        let sanitized = sanitize_error(&err);
74
75        assert_eq!(sanitized, "Internal server error");
76        assert!(!sanitized.contains("DynamoDB"));
77        assert!(!sanitized.contains("10.0.1.5"));
78    }
79
80    #[test]
81    fn test_sanitize_error_with_custom_message() {
82        let err = anyhow!("AWS IAM credentials not found");
83        let sanitized = sanitize_error_with_message(&err, "Service temporarily unavailable");
84
85        assert_eq!(sanitized, "Service temporarily unavailable");
86        assert!(!sanitized.contains("IAM"));
87        assert!(!sanitized.contains("credentials"));
88    }
89
90    #[test]
91    fn test_safe_errors_are_identified() {
92        let validation_err = anyhow!("Invalid tunnel ID format: ABC");
93        assert!(is_safe_error(&validation_err));
94
95        let timeout_err = anyhow!("Request timeout waiting for response");
96        assert!(is_safe_error(&timeout_err));
97
98        let db_err = anyhow!("DynamoDB throttling error");
99        assert!(!is_safe_error(&db_err));
100    }
101
102    #[test]
103    fn test_client_error_message() {
104        // Safe error should be returned as-is
105        let safe_err = anyhow!("Invalid tunnel ID format: test");
106        let msg = get_client_error_message(&safe_err);
107        assert!(msg.contains("Invalid tunnel ID"));
108
109        // Unsafe error should be sanitized
110        let unsafe_err = anyhow!("AWS SDK error: InvalidParameterException");
111        let msg = get_client_error_message(&unsafe_err);
112        assert_eq!(msg, "Internal server error");
113        assert!(!msg.contains("AWS"));
114    }
115}