use crate::config::Config; use regex::Regex; use std::path::Path; pub fn should_skip(path: &Path, cfg: &Config) -> bool { if !cfg.redact { return false; } let lower = path.to_string_lossy().to_lowercase(); // sensitive dirs for seg in [".ssh", ".gnupg", ".pki", ".password-store", "monero", ".bitmonero"] { if lower.contains(seg) { return true; } } if let Some(name) = path.file_name().and_then(|s| s.to_str()) { let n = name.to_lowercase(); if n == ".env" || n.ends_with(".key") || n.ends_with(".pem") || n.ends_with(".p12") || n.ends_with(".pfx") || n.contains("secret") || n.contains("credential") || n.contains("password") || n == "id_rsa" || n == "id_ed25519" || n == "id_ecdsa" || n == "known_hosts" { return true; } } false } pub fn redact(text: &str) -> String { let patterns = [ // private key blocks (r"-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----", "[REDACTED_PRIVATE_KEY]"), // github tokens (r"gh[pousr]_[A-Za-z0-9]{30,}", "[REDACTED_GITHUB_TOKEN]"), // aws access key (r"AKIA[0-9A-Z]{16}", "[REDACTED_AWS_KEY]"), // generic api key / token assignments (r#"(?i)(api[_-]?key|token|secret|password)\s*[:=]\s*["']?[A-Za-z0-9_\-]{16,}["']?"#, "$1=[REDACTED]"), // emails? keep emails (useful for profile) but redact passwords nearby handled // monero/seed-like 25 word phrases not attempted ]; let mut s = text.to_string(); for (p, r) in patterns { if let Ok(re) = Regex::new(p) { s = re.replace_all(&s, r).to_string(); } } s }