diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index f2d48db34..9ac9c7fa4 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -2485,6 +2485,13 @@ dependencies = [ "pretty_assertions", ] +[[package]] +name = "codex-utils-sanitizer" +version = "0.0.0" +dependencies = [ + "regex", +] + [[package]] name = "codex-utils-string" version = "0.0.0" diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index ab7e131b3..b026a2ca1 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -53,6 +53,7 @@ members = [ "utils/cli", "utils/elapsed", "utils/sandbox-summary", + "utils/sanitizer", "utils/approval-presets", "utils/oss", "utils/fuzzy-match", @@ -129,6 +130,7 @@ codex-utils-pty = { path = "utils/pty" } codex-utils-readiness = { path = "utils/readiness" } codex-utils-rustls-provider = { path = "utils/rustls-provider" } codex-utils-sandbox-summary = { path = "utils/sandbox-summary" } +codex-utils-sanitizer = { path = "utils/sanitizer" } codex-utils-string = { path = "utils/string" } codex-windows-sandbox = { path = "windows-sandbox-rs" } core_test_support = { path = "core/tests/common" } @@ -338,6 +340,7 @@ ignored = [ "icu_provider", "openssl-sys", "codex-utils-readiness", + "codex-utils-sanitizer", "codex-secrets", ] diff --git a/codex-rs/utils/sanitizer/Cargo.toml b/codex-rs/utils/sanitizer/Cargo.toml new file mode 100644 index 000000000..6f96cc891 --- /dev/null +++ b/codex-rs/utils/sanitizer/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "codex-utils-sanitizer" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +regex = "1.12.3" + +[lints] +workspace = true diff --git a/codex-rs/utils/sanitizer/src/lib.rs b/codex-rs/utils/sanitizer/src/lib.rs new file mode 100644 index 000000000..7f112b2f0 --- /dev/null +++ b/codex-rs/utils/sanitizer/src/lib.rs @@ -0,0 +1,41 @@ +use regex::Regex; +use std::sync::LazyLock; + +static OPENAI_KEY_REGEX: LazyLock = LazyLock::new(|| compile_regex(r"sk-[A-Za-z0-9]{20,}")); +static AWS_ACCESS_KEY_ID_REGEX: LazyLock = + LazyLock::new(|| compile_regex(r"\bAKIA[0-9A-Z]{16}\b")); +static BEARER_TOKEN_REGEX: LazyLock = + LazyLock::new(|| compile_regex(r"(?i)\bBearer\s+[A-Za-z0-9._\-]{16,}\b")); +static SECRET_ASSIGNMENT_REGEX: LazyLock = LazyLock::new(|| { + compile_regex(r#"(?i)\b(api[_-]?key|token|secret|password)\b(\s*[:=]\s*)(["']?)[^\s"']{8,}"#) +}); + +/// Remove secret and keys from a String. This is done on best effort basis following some +/// well-known REGEX. +pub fn redact_secrets(input: String) -> String { + let redacted = OPENAI_KEY_REGEX.replace_all(&input, "[REDACTED_SECRET]"); + let redacted = AWS_ACCESS_KEY_ID_REGEX.replace_all(&redacted, "[REDACTED_SECRET]"); + let redacted = BEARER_TOKEN_REGEX.replace_all(&redacted, "Bearer [REDACTED_SECRET]"); + let redacted = SECRET_ASSIGNMENT_REGEX.replace_all(&redacted, "$1$2$3[REDACTED_SECRET]"); + + redacted.to_string() +} + +fn compile_regex(pattern: &str) -> Regex { + match Regex::new(pattern) { + Ok(regex) => regex, + // Panic is ok thanks to `load_regex` test. + Err(err) => panic!("invalid regex pattern `{pattern}`: {err}"), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn load_regex() { + // The goal of this test is just to compile all the regex to prevent the panic + let _ = redact_secrets("secret".to_string()); + } +}