From bab7c2dcc1328765c282b0011207945477f08a93 Mon Sep 17 00:00:00 2001 From: Alec Barber Date: Tue, 4 Aug 2026 15:01:14 +0000 Subject: [PATCH] Improve bearer token secret redaction (#36908) ## Why Bearer credentials can contain characters outside the previous token pattern or start with another recognized key prefix. This could leave part of the credential visible after redaction. ## What changed - Recognize bearer tokens containing URL-safe and base64-style characters, optional padding, and horizontal whitespace after the scheme. - Redact bearer credentials before matching narrower OpenAI and AWS key forms so the whole credential is replaced. - Preserve delimiters following the credential and avoid matching short tokens, joined scheme names, or tokens separated by vertical or non-ASCII whitespace. ## Testing Added focused positive and negative cases for the supported token forms and false-positive boundaries. GitOrigin-RevId: 1e5b10685281dc91d232d619b938bacbadaa47f9 --- codex-rs/secrets/src/sanitizer.rs | 60 +++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/codex-rs/secrets/src/sanitizer.rs b/codex-rs/secrets/src/sanitizer.rs index 7f112b2f0fa1..0b1b251242bf 100644 --- a/codex-rs/secrets/src/sanitizer.rs +++ b/codex-rs/secrets/src/sanitizer.rs @@ -5,7 +5,7 @@ static OPENAI_KEY_REGEX: LazyLock = LazyLock::new(|| compile_regex(r"sk-[ 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")); + LazyLock::new(|| compile_regex(r"(?i:\bBearer)[ \t]+[A-Za-z0-9._~+/-]{16,}=*")); static SECRET_ASSIGNMENT_REGEX: LazyLock = LazyLock::new(|| { compile_regex(r#"(?i)\b(api[_-]?key|token|secret|password)\b(\s*[:=]\s*)(["']?)[^\s"']{8,}"#) }); @@ -13,9 +13,9 @@ static SECRET_ASSIGNMENT_REGEX: LazyLock = LazyLock::new(|| { /// 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 = BEARER_TOKEN_REGEX.replace_all(&input, "Bearer [REDACTED_SECRET]"); + let redacted = OPENAI_KEY_REGEX.replace_all(&redacted, "[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() @@ -24,7 +24,6 @@ pub fn redact_secrets(input: String) -> 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}"), } } @@ -32,10 +31,57 @@ fn compile_regex(pattern: &str) -> Regex { #[cfg(test)] mod tests { use super::*; + use pretty_assertions::assert_eq; #[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()); + fn redacts_supported_bearer_tokens() { + let cases = [ + ( + "Bearer abcde+fghijklmnopqrstuvwxyz012345", + "Bearer [REDACTED_SECRET]", + ), + ( + "Bearer abcdefghijklmnop+secret_suffix", + "Bearer [REDACTED_SECRET]", + ), + ( + "Bearer sk-abcdefghijklmnopqrst+secret_suffix", + "Bearer [REDACTED_SECRET]", + ), + ( + "Bearer AKIAABCDEFGHIJKLMNOP/~secret_suffix", + "Bearer [REDACTED_SECRET]", + ), + ( + "Bearer AbcdefghijklMN09._~+/-==; echo done", + "Bearer [REDACTED_SECRET]; echo done", + ), + ( + "authorization: bEaReR\tabcdefghijklmnop", + "authorization: Bearer [REDACTED_SECRET]", + ), + ("Bearer abcdefghijklmnop", "Bearer [REDACTED_SECRET]"), + ]; + + for (input, expected) in cases { + assert_eq!(redact_secrets(input.to_string()), expected); + } + } + + #[test] + fn avoids_bearer_false_positives() { + let cases = [ + "Bearer of good news", + "Bearer abcdefghijklmno", + "NotABearer abcdefghijklmnop", + "Bearerabcdefghijklmnop", + "Bearer\nabcdefghijklmnop", + "Bearer\u{a0}abcdefghijklmnop", + "Bearer abcdefghijklmno\u{212a}", + ]; + + for input in cases { + assert_eq!(redact_secrets(input.to_string()), input); + } } }