vtcode_commons/
sanitizer.rs1use regex::Regex;
13use std::sync::LazyLock;
14
15static OPENAI_KEY_REGEX: LazyLock<Regex> = LazyLock::new(|| compile_regex(r"sk-[A-Za-z0-9]{20,}"));
17
18static AWS_ACCESS_KEY_ID_REGEX: LazyLock<Regex> = LazyLock::new(|| compile_regex(r"\bAKIA[0-9A-Z]{16}\b"));
20
21static BEARER_TOKEN_REGEX: LazyLock<Regex> = LazyLock::new(|| compile_regex(r"(?i)\bBearer\s+[A-Za-z0-9.\-_]{16,}\b"));
23
24static SECRET_ASSIGNMENT_REGEX: LazyLock<Regex> =
27 LazyLock::new(|| compile_regex(r#"(?i)\b(api[\-_]?key|token|secret|password)\b(\s*[:=]\s*)(["']?)[^\s"']{8,}"#));
28
29pub fn redact_secrets(input: String) -> String {
44 let r1 = OPENAI_KEY_REGEX.replace_all(&input, "[REDACTED_SECRET]");
45 let r2 = AWS_ACCESS_KEY_ID_REGEX.replace_all(&r1, "[REDACTED_SECRET]");
46 let r3 = BEARER_TOKEN_REGEX.replace_all(&r2, "Bearer [REDACTED_SECRET]");
47 let r4 = SECRET_ASSIGNMENT_REGEX.replace_all(&r3, "$1$2$3[REDACTED_SECRET]");
48 r4.into_owned()
54}
55
56#[allow(clippy::panic)]
57fn compile_regex(pattern: &str) -> Regex {
58 match Regex::new(pattern) {
59 Ok(regex) => regex,
60 Err(err) => panic!("invalid regex pattern `{pattern}`: {err}"),
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn load_regex() {
71 let _ = redact_secrets("test".to_string());
73 }
74
75 #[test]
76 fn redacts_openai_key() {
77 let input = "Found key: sk-test1234567890abcdefghij".to_string();
78 let output = redact_secrets(input);
79 assert_eq!(output, "Found key: [REDACTED_SECRET]");
80 }
81
82 #[test]
83 fn redacts_aws_access_key() {
84 let input = " creds: AKIAIOSFODNN7EXAMPLE ".to_string();
86 let output = redact_secrets(input);
87 assert_eq!(output, " creds: [REDACTED_SECRET] ");
88 }
89
90 #[test]
91 fn redacts_bearer_token() {
92 let input = "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9".to_string();
93 let output = redact_secrets(input);
94 assert_eq!(output, "Authorization: Bearer [REDACTED_SECRET]");
95 }
96
97 #[test]
98 fn redacts_api_key_assignment() {
99 let input = "api_key=sk-test12345678".to_string();
100 let output = redact_secrets(input);
101 assert_eq!(output, "api_key=[REDACTED_SECRET]");
102 }
103
104 #[test]
105 fn redacts_password_assignment() {
106 let input = "password: mysecretvalue".to_string();
107 let output = redact_secrets(input);
108 assert_eq!(output, "password: [REDACTED_SECRET]");
109 }
110
111 #[test]
112 fn redacts_token_in_quotes() {
113 let input = r#"token="abc123xyz789abcdef""#.to_string();
114 let output = redact_secrets(input);
115 assert_eq!(output, r#"token="[REDACTED_SECRET]""#);
116 }
117
118 #[test]
119 fn preserves_short_values() {
120 let input = "password: short".to_string();
122 let output = redact_secrets(input);
123 assert_eq!(output, "password: short");
124 }
125
126 #[test]
127 fn redacts_multiple_secrets() {
128 let input = "Keys: sk-test1234567890abcdefghij and AKIAIOSFODNN7EXAMPLE".to_string();
129 let output = redact_secrets(input);
130 assert!(output.contains("[REDACTED_SECRET]"));
132 assert!(!output.contains("AKIAIOSFODNN7EXAMPLE"));
133 assert!(!output.contains("sk-test1234567890abcdefghij"));
134 }
135
136 #[test]
137 fn preserves_non_secret_text() {
138 let input = "Hello world, this is normal text".to_string();
139 let output = redact_secrets(input);
140 assert_eq!(output, "Hello world, this is normal text");
141 }
142}