redact.rs

 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
 7/// Whether a given environment variable name should have its value redacted
 8pub 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
23/// Redact a string which could include a command with environment variables
24pub fn redact_command(command: &str) -> String {
25    REDACT_REGEX
26        .replace_all(command, |caps: &regex::Captures| {
27            let var_name = &caps[1];
28            let value = &caps[2];
29            if should_redact(var_name) {
30                format!(r#"{}="[REDACTED]""#, var_name)
31            } else {
32                format!("{}={}", var_name, value)
33            }
34        })
35        .to_string()
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn test_redact_string_with_multiple_env_vars() {
44        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""#;
45        let result = redact_command(input);
46        let expected = r#"failed to spawn command cd "/code/something" && ANTHROPIC_API_KEY="[REDACTED]" COMMAND_MODE="unix2003" GEMINI_API_KEY="[REDACTED]" HOME="/Users/foo""#;
47        assert_eq!(result, expected);
48    }
49}