Skip to main content

util/
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
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
30/// Redact a string which could include a command with environment variables
31pub fn redact_command(command: &str) -> String {
32    REDACT_REGEX
33        .replace_all(command, |caps: &regex::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}