use lazy_static::lazy_static;
use regex::Regex;
lazy_static! {
static ref TOKEN_PATTERNS: Vec<Regex> = vec![
Regex::new(r"ghp_[A-Za-z0-9]{36,}").unwrap(),
Regex::new(r"gho_[A-Za-z0-9]{36,}").unwrap(),
Regex::new(r"ghu_[A-Za-z0-9]{36,}").unwrap(),
Regex::new(r"ghs_[A-Za-z0-9]{36,}").unwrap(),
Regex::new(r"github_pat_[A-Za-z0-9_]{22,}").unwrap(),
Regex::new(r"glpat-[A-Za-z0-9\-_.]{20,}").unwrap(),
Regex::new(r"gldt-[A-Za-z0-9\-_.]{20,}").unwrap(),
Regex::new(r"(?i)(Bearer|Token|PRIVATE-TOKEN:?)\s+[A-Za-z0-9\-_.]{20,}").unwrap(),
Regex::new(r"://[^@\s]+@").unwrap(),
];
}
pub fn sanitize_output(input: &str) -> String {
let mut result = input.to_string();
for pattern in TOKEN_PATTERNS.iter() {
result = pattern.replace_all(&result, "[REDACTED]").to_string();
}
result
}
pub fn sanitize_with_known_tokens(input: &str, known_tokens: &[&str]) -> String {
let mut result = sanitize_output(input);
for token in known_tokens {
if !token.is_empty() {
result = result.replace(token, "[REDACTED]");
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sanitize_github_pat() {
let input = "token: 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";
let result = sanitize_output(input);
assert!(result.contains("[REDACTED]"));
assert!(!result.contains("ghp_"));
}
#[test]
fn test_sanitize_gitlab_pat() {
let input = "auth: glpat-xxxxxxxxxxxxxxxxxxxx";
let result = sanitize_output(input);
assert!(result.contains("[REDACTED]"));
assert!(!result.contains("glpat-"));
}
#[test]
fn test_sanitize_fine_grained_github() {
let input =
"github_pat_11AAAAAA0xxxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
let result = sanitize_output(input);
assert!(result.contains("[REDACTED]"));
assert!(!result.contains("github_pat_"));
}
#[test]
fn test_sanitize_bearer_header() {
let input = "Authorization: Bearer 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";
let result = sanitize_output(input);
assert!(!result.contains("ghp_"));
}
#[test]
fn test_sanitize_preserves_normal_text() {
let input = "Pushed to origin/main successfully. 3 commits transferred.";
let result = sanitize_output(input);
assert_eq!(result, input);
}
#[test]
fn test_sanitize_url_with_embedded_creds() {
let input = "remote: http://user:mysecrettoken@gitlab.example.com/repo.git";
let result = sanitize_output(input);
assert!(!result.contains("mysecrettoken"));
assert!(result.contains("[REDACTED]"));
}
#[test]
fn test_sanitize_with_known_tokens() {
let input = "Server responded with: abc123secretvalue in the output";
let result = sanitize_with_known_tokens(input, &["abc123secretvalue"]);
assert!(!result.contains("abc123secretvalue"));
assert!(result.contains("[REDACTED]"));
}
#[test]
fn test_sanitize_with_empty_known_tokens() {
let input = "Normal output text";
let result = sanitize_with_known_tokens(input, &[""]);
assert_eq!(result, input);
}
#[test]
fn test_sanitize_multiple_tokens_in_one_string() {
let input =
"ghp_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa and glpat-bbbbbbbbbbbbbbbbbbbbbbbb";
let result = sanitize_output(input);
assert!(!result.contains("ghp_"));
assert!(!result.contains("glpat-"));
assert_eq!(result.matches("[REDACTED]").count(), 2);
}
#[test]
fn test_sanitize_gitlab_deploy_token() {
let input = "deploy: gldt-xxxxxxxxxxxxxxxxxxxx";
let result = sanitize_output(input);
assert!(!result.contains("gldt-"));
assert!(result.contains("[REDACTED]"));
}
#[test]
fn test_sanitize_private_token_header() {
let input = "PRIVATE-TOKEN: glpat-xxxxxxxxxxxxxxxxxxxx";
let result = sanitize_output(input);
assert!(!result.contains("glpat-"));
}
}