subx_cli/config/
masking.rs1pub fn mask_sensitive_value(key: &str, value: &str) -> String {
20 let key_lower = key.to_lowercase();
21 let is_sensitive = key_lower.contains("api_key")
22 || key_lower.contains("token")
23 || key_lower.contains("secret");
24
25 if !is_sensitive {
26 return value.to_string();
27 }
28
29 if value.is_empty() {
30 return String::new();
31 }
32
33 if value.chars().count() <= 4 {
34 "****".to_string()
35 } else {
36 let tail: String = value
37 .chars()
38 .rev()
39 .take(4)
40 .collect::<Vec<_>>()
41 .into_iter()
42 .rev()
43 .collect();
44 format!("****{tail}")
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 #[test]
53 fn non_sensitive_key_returns_value_unchanged() {
54 assert_eq!(mask_sensitive_value("ai.model", "gpt-4"), "gpt-4");
55 assert_eq!(mask_sensitive_value("formats.default_output", "srt"), "srt");
56 }
57
58 #[test]
59 fn short_sensitive_value_is_fully_masked() {
60 assert_eq!(mask_sensitive_value("ai.api_key", "abcd"), "****");
61 assert_eq!(mask_sensitive_value("ai.api_key", "a"), "****");
62 }
63
64 #[test]
65 fn long_sensitive_value_preserves_last_four() {
66 assert_eq!(
67 mask_sensitive_value("ai.api_key", "sk-1234567890abcdef"),
68 "****cdef"
69 );
70 }
71
72 #[test]
73 fn empty_sensitive_value_stays_empty() {
74 assert_eq!(mask_sensitive_value("ai.api_key", ""), "");
75 }
76
77 #[test]
78 fn matching_is_case_insensitive_and_substring() {
79 assert_eq!(mask_sensitive_value("AI.API_KEY", "abcdefgh"), "****efgh");
80 assert_eq!(
81 mask_sensitive_value("auth.access_token", "tokenvalue12345"),
82 "****2345"
83 );
84 assert_eq!(
85 mask_sensitive_value("client.secret", "supersecretvalue"),
86 "****alue"
87 );
88 }
89}