use regex::Regex;
use std::borrow::Cow;
use std::sync::LazyLock;
const KV_VALUE: &str = r#"("[^"]*"|'[^']*'|\S+)"#;
#[allow(clippy::panic)]
fn compile(tag: &'static str, source: &str) -> Regex {
Regex::new(source).unwrap_or_else(|e| {
panic!(
"santh-error::redact: secret pattern `{tag}` failed to compile: {e}. \
Fix: correct the regex source in `redact.rs` for `{tag}`."
)
})
}
static SECRET_PATTERNS: LazyLock<Vec<Regex>> = LazyLock::new(|| {
vec![
compile("aws_access_key", r"A[KS]IA[0-9A-Z]{16}"),
compile("github_pat_classic", r"gh[pousr]_[A-Za-z0-9_]{36,}"),
compile("github_pat_fine", r"github_pat_[A-Za-z0-9_]{22,}"),
compile(
"jwt",
r"eyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*",
),
compile("bearer", r"(?i)Bearer\s+[A-Za-z0-9_.-]+"),
compile("password_kv", &format!(r"(?i)(?:pass(?:word|wd|code)|passphrase)\s*[=:]\s*{KV_VALUE}")),
compile("api_key_kv", &format!(r"(?i)(?:api|secret|access|private|master|signing|encryption|auth|session)[_-]?key\s*[=:]\s*{KV_VALUE}")),
compile("token_kv", &format!(r"(?i)(?:[a-z0-9_-]+[_-])?token\s*[=:]\s*{KV_VALUE}")),
compile("secret_kv", &format!(r"(?i)(?:[a-z0-9_-]+[_-])?secret(?:[_-][a-z0-9_-]+)?\s*[=:]\s*{KV_VALUE}")),
compile("slack_token", r"xox[baprs]-[a-zA-Z0-9_-]{10,}"),
compile("gcp_api_key", r"AIzaSy[A-Za-z0-9_-]{33}"),
compile("stripe_api_key", r"(?:sk|rk)_(?:live|test)_[0-9a-zA-Z]{24,}"),
compile("openai_api_key", r"sk-[a-zA-Z0-9_-]{20,}"),
compile(
"pem_private_key",
r"-----BEGIN (RSA |DSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (RSA |DSA |EC |OPENSSH )?PRIVATE KEY-----",
),
]
});
static URL_USERINFO: LazyLock<Regex> =
LazyLock::new(|| compile("url_userinfo", r"://[^/@\s]*:[^/@\s]*@"));
pub fn redact_secrets(input: &str) -> String {
let mut output = input.to_string();
for pattern in SECRET_PATTERNS.iter() {
if let Cow::Owned(replaced) = pattern.replace_all(&output, "[REDACTED]") {
output = replaced;
}
}
if let Cow::Owned(replaced) = URL_USERINFO.replace_all(&output, "://***@") {
output = replaced;
}
output
}