profileme / src/redact.rs

raw file 1848 bytes
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
}