1use std::sync::LazyLock;
2
3static REDACT_REGEX: LazyLock<regex::Regex> = LazyLock::new(|| {
4 regex::Regex::new(r#"([A-Z_][A-Z0-9_]*)=("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\S+)"#).unwrap()
5});
6
7pub fn should_redact(env_var_name: &str) -> bool {
9 const REDACTED_SUFFIXES: &[&str] = &[
10 "KEY",
11 "TOKEN",
12 "PASSWORD",
13 "SECRET",
14 "PASS",
15 "CREDENTIALS",
16 "LICENSE",
17 ];
18 REDACTED_SUFFIXES
19 .iter()
20 .any(|suffix| env_var_name.ends_with(suffix))
21}
22
23pub fn is_valid_environment_name(name: &str) -> bool {
24 !name.is_empty()
25 && name
26 .chars()
27 .all(|character| character != '=' && !character.is_control())
28}
29
30pub fn redact_command(command: &str) -> String {
32 REDACT_REGEX
33 .replace_all(command, |caps: ®ex::Captures| {
34 let var_name = &caps[1];
35 let value = &caps[2];
36 if should_redact(var_name) {
37 format!(r#"{}="[REDACTED]""#, var_name)
38 } else {
39 format!("{}={}", var_name, value)
40 }
41 })
42 .to_string()
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48
49 #[test]
50 fn test_redact_string_with_multiple_env_vars() {
51 let input = r#"failed to spawn command cd "/code/something" && ANTHROPIC_API_KEY="sk-ant-api03-WOOOO" COMMAND_MODE="unix2003" GEMINI_API_KEY="AIGEMINIFACE" HOME="/Users/foo""#;
52 let result = redact_command(input);
53 let expected = r#"failed to spawn command cd "/code/something" && ANTHROPIC_API_KEY="[REDACTED]" COMMAND_MODE="unix2003" GEMINI_API_KEY="[REDACTED]" HOME="/Users/foo""#;
54 assert_eq!(result, expected);
55 }
56}