cloakrs-patterns 0.3.0

Universal PII recognizers for cloakrs
Documentation
use cloakrs_core::Recognizer;
use cloakrs_patterns::{
    ApiKeyRecognizer, AwsAccessKeyRecognizer, CreditCardRecognizer, CryptoAddressRecognizer,
    DateOfBirthRecognizer, EmailRecognizer, HostnameRecognizer, IbanRecognizer,
    IpAddressRecognizer, JwtRecognizer, MacAddressRecognizer, PersonNameRecognizer,
    PhoneRecognizer, PhysicalAddressRecognizer, SsnRecognizer, UrlRecognizer, UserPathRecognizer,
};

const CORPUS_LINES: usize = 10_000;

#[test]
fn test_false_positive_corpus_has_no_default_pattern_findings() {
    let corpus = no_pii_corpus(CORPUS_LINES);
    let recognizers: Vec<(&str, Box<dyn Recognizer>)> = vec![
        ("email", Box::new(EmailRecognizer)),
        ("phone", Box::new(PhoneRecognizer)),
        ("credit_card", Box::new(CreditCardRecognizer)),
        ("iban", Box::new(IbanRecognizer)),
        ("ssn", Box::new(SsnRecognizer)),
        ("ip_address", Box::new(IpAddressRecognizer)),
        ("url", Box::new(UrlRecognizer)),
        ("hostname", Box::new(HostnameRecognizer)),
        ("user_path", Box::new(UserPathRecognizer)),
        ("person_name", Box::new(PersonNameRecognizer)),
        ("physical_address", Box::new(PhysicalAddressRecognizer)),
        ("aws_access_key", Box::new(AwsAccessKeyRecognizer)),
        ("jwt", Box::new(JwtRecognizer)),
        ("api_key", Box::new(ApiKeyRecognizer)),
        ("mac_address", Box::new(MacAddressRecognizer)),
        ("crypto_address", Box::new(CryptoAddressRecognizer)),
        ("date_of_birth", Box::new(DateOfBirthRecognizer)),
    ];

    let mut failures = Vec::new();
    for (name, recognizer) in recognizers {
        let findings = recognizer.scan(&corpus);
        if !findings.is_empty() {
            failures.push(format!(
                "{name}: {} false positives, first={:?}",
                findings.len(),
                findings.first()
            ));
        }
    }

    assert!(
        failures.is_empty(),
        "false-positive corpus produced findings:\n{}",
        failures.join("\n")
    );
}

fn no_pii_corpus(lines: usize) -> String {
    let modules = [
        "cache", "queue", "worker", "parser", "render", "search", "billing", "metrics",
    ];
    let actions = [
        "started", "finished", "skipped", "retried", "queued", "merged", "indexed", "stored",
    ];
    let statuses = [
        "ok", "warm", "cold", "idle", "busy", "ready", "clean", "dry",
    ];

    let mut corpus = String::with_capacity(lines * 96);
    for index in 0..lines {
        let module = modules[index % modules.len()];
        let action = actions[(index / 3) % actions.len()];
        let status = statuses[(index / 7) % statuses.len()];
        corpus.push_str(&format!(
            "event id {index:05} module {module} action {action} status {status} count {} duration {}ms batch ref{:06}\n",
            20 + (index % 700),
            5 + (index % 400),
            100_000 + index
        ));
    }
    corpus
}