laminar_connectors/storage/masking/
mod.rs1#![allow(clippy::disallowed_types)] use std::collections::HashMap;
9
10const SECRET_PATTERNS: &[&str] = &[
14 "secret",
15 "password",
16 "access_key",
17 "account_key",
18 "private_key",
19 "token",
20 "credential",
21 "service_account",
22 "client_id",
23 "tenant_id",
24 "account_name",
25 "profile",
26 "role_arn",
27 "authorization",
28 "kms_key",
29];
30
31pub struct SecretMasker;
33
34impl SecretMasker {
35 #[must_use]
52 pub fn is_secret_key(key: &str) -> bool {
53 let lower = key.to_lowercase();
54 SECRET_PATTERNS.iter().any(|p| lower.contains(p))
55 }
56
57 #[must_use]
59 pub fn redact_map(map: &HashMap<String, String>) -> HashMap<String, String> {
60 map.iter()
61 .map(|(k, v)| (k.clone(), Self::redact_value(k, v)))
62 .collect()
63 }
64
65 #[must_use]
67 pub fn display_map(map: &HashMap<String, String>) -> String {
68 if map.is_empty() {
69 return String::new();
70 }
71
72 let mut pairs: Vec<_> = map.iter().collect();
73 pairs.sort_by_key(|(k, _)| k.as_str());
74 pairs
75 .iter()
76 .map(|(k, v)| format!("{k}={}", Self::redact_value(k, v)))
77 .collect::<Vec<_>>()
78 .join(", ")
79 }
80
81 fn redact_value(key: &str, value: &str) -> String {
82 if Self::is_secret_key(key) {
83 return "***".into();
84 }
85 let normalized = key.to_ascii_lowercase();
86 if normalized.contains("endpoint") || normalized.ends_with("base_url") {
87 return endpoint_description(value);
88 }
89 if value.contains("://") {
90 if let Ok(parsed) = url::Url::parse(value) {
91 if !parsed.username().is_empty()
92 || parsed.password().is_some()
93 || parsed.query().is_some()
94 || parsed.fragment().is_some()
95 {
96 return "<redacted-url>".into();
97 }
98 } else if value.contains('?') || value.contains('@') || value.contains('#') {
99 return "<redacted-url>".into();
100 }
101 }
102 value.to_string()
103 }
104}
105
106fn endpoint_description(value: &str) -> String {
107 let Ok(parsed) = url::Url::parse(value) else {
108 return "<configured-endpoint>".into();
109 };
110 match parsed.scheme() {
111 "http" => "<custom-http-endpoint>".into(),
112 "https" => "<custom-https-endpoint>".into(),
113 _ => "<configured-endpoint>".into(),
114 }
115}
116
117#[cfg(test)]
118mod tests;