Skip to main content

eggress_system_proxy/
redaction.rs

1use std::collections::HashMap;
2
3/// Redact sensitive information from proxy URIs and settings.
4///
5/// Removes passwords from `user:pass@host` patterns and replaces
6/// them with `***`. Preserves the rest of the URI structure.
7pub fn redact_proxy_uri(uri: &str) -> String {
8    if let Some(at_pos) = uri.rfind('@') {
9        let prefix = &uri[..at_pos];
10        let suffix = &uri[at_pos..]; // includes @
11        if let Some(colon_pos) = prefix.rfind(':') {
12            let before = &uri[..colon_pos];
13            return format!("{}:***{}", before, suffix);
14        }
15    }
16    uri.to_string()
17}
18
19/// Redact proxy settings map values.
20///
21/// Keys containing "proxy" (case-insensitive) have their values
22/// processed through `redact_proxy_uri`.
23pub fn redact_proxy_settings(settings: &HashMap<String, String>) -> HashMap<String, String> {
24    settings
25        .iter()
26        .map(|(k, v)| {
27            if k.to_lowercase().contains("proxy") {
28                (k.clone(), redact_proxy_uri(v))
29            } else {
30                (k.clone(), v.clone())
31            }
32        })
33        .collect()
34}
35
36/// Redact a list of proxy URIs.
37pub fn redact_proxy_uris(uris: &[String]) -> Vec<String> {
38    uris.iter().map(|u| redact_proxy_uri(u)).collect()
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn redact_uri_with_credentials() {
47        assert_eq!(
48            redact_proxy_uri("http://user:secret@proxy.example.com:8080"),
49            "http://user:***@proxy.example.com:8080"
50        );
51    }
52
53    #[test]
54    fn redact_uri_without_credentials() {
55        assert_eq!(
56            redact_proxy_uri("http://proxy.example.com:8080"),
57            "http://proxy.example.com:8080"
58        );
59    }
60
61    #[test]
62    fn redact_uri_socks_with_credentials() {
63        assert_eq!(
64            redact_proxy_uri("socks5://admin:password123@127.0.0.1:1080"),
65            "socks5://admin:***@127.0.0.1:1080"
66        );
67    }
68
69    #[test]
70    fn redact_uri_no_at_sign() {
71        assert_eq!(
72            redact_proxy_uri("http://proxy.example.com:8080"),
73            "http://proxy.example.com:8080"
74        );
75    }
76
77    #[test]
78    fn redact_settings_map() {
79        let mut settings = HashMap::new();
80        settings.insert(
81            "http_proxy".to_string(),
82            "http://user:pass@proxy:8080".to_string(),
83        );
84        settings.insert("no_proxy".to_string(), "localhost,127.0.0.1".to_string());
85        settings.insert(
86            "HTTP_PROXY".to_string(),
87            "http://admin:secret@proxy:8080".to_string(),
88        );
89
90        let redacted = redact_proxy_settings(&settings);
91        assert_eq!(
92            redacted.get("http_proxy").unwrap(),
93            "http://user:***@proxy:8080"
94        );
95        assert_eq!(redacted.get("no_proxy").unwrap(), "localhost,127.0.0.1");
96        assert_eq!(
97            redacted.get("HTTP_PROXY").unwrap(),
98            "http://admin:***@proxy:8080"
99        );
100    }
101
102    #[test]
103    fn redact_uris_list() {
104        let uris = vec![
105            "http://user:secret@proxy:8080".to_string(),
106            "http://proxy:8080".to_string(),
107        ];
108        let redacted = redact_proxy_uris(&uris);
109        assert_eq!(redacted[0], "http://user:***@proxy:8080");
110        assert_eq!(redacted[1], "http://proxy:8080");
111    }
112}