leaktor 0.3.0

A secrets scanner with pattern matching, entropy analysis, and live validation
Documentation
pub mod anthropic;
pub mod aws;
pub mod datadog;
pub mod github;
pub mod gitlab;
pub mod http;
pub mod huggingface;
pub mod openai;
pub mod sendgrid;
pub mod slack;
pub mod stripe;

pub use anthropic::AnthropicValidator;
pub use aws::AwsValidator;
pub use datadog::DatadogValidator;
pub use github::GitHubValidator;
pub use gitlab::GitLabValidator;
pub use http::HttpValidator;
pub use huggingface::HuggingFaceValidator;
pub use openai::OpenAiValidator;
pub use sendgrid::SendGridValidator;
pub use slack::SlackValidator;
pub use stripe::StripeValidator;

use crate::models::{Secret, SecretType};
use anyhow::Result;

/// Trait for validating secrets
#[async_trait::async_trait]
pub trait Validator {
    async fn validate(&self, secret: &Secret) -> Result<bool>;
    fn supports(&self, secret_type: &SecretType) -> bool;
}

/// Create the full list of all available validators
fn all_validators() -> Vec<Box<dyn Validator + Send + Sync>> {
    vec![
        Box::new(AwsValidator::new()),
        Box::new(GitHubValidator::new()),
        Box::new(GitLabValidator::new()),
        Box::new(SlackValidator::new()),
        Box::new(StripeValidator::new()),
        Box::new(OpenAiValidator::new()),
        Box::new(AnthropicValidator::new()),
        Box::new(SendGridValidator::new()),
        Box::new(DatadogValidator::new()),
        Box::new(HuggingFaceValidator::new()),
    ]
}

/// Validate a secret using the appropriate validator
pub async fn validate_secret(secret: &mut Secret) -> Result<()> {
    let validators = all_validators();

    for validator in validators {
        if validator.supports(&secret.secret_type) {
            match validator.validate(secret).await {
                Ok(is_valid) => {
                    secret.validated = Some(is_valid);
                    return Ok(());
                }
                Err(_) => {
                    // Validation failed (network error, etc.), mark as unknown
                    secret.validated = None;
                }
            }
        }
    }

    Ok(())
}

/// Validate multiple secrets in parallel using tokio tasks
pub async fn validate_secrets_parallel(secrets: &mut [Secret]) -> Result<()> {
    use tokio::task::JoinSet;

    let validators = all_validators();

    // Build a list of (index, secret_clone) pairs that have a matching validator
    let mut tasks: JoinSet<(usize, Option<bool>)> = JoinSet::new();

    for (idx, secret) in secrets.iter().enumerate() {
        // Find which validator supports this secret type
        let mut found_validator = false;
        for v in &validators {
            if v.supports(&secret.secret_type) {
                found_validator = true;
                break;
            }
        }

        if !found_validator {
            continue;
        }

        let secret_clone = secret.clone();
        tasks.spawn(async move {
            // Re-create validators inside the task (they're cheap)
            let validators = all_validators();

            for validator in &validators {
                if validator.supports(&secret_clone.secret_type) {
                    match validator.validate(&secret_clone).await {
                        Ok(is_valid) => return (idx, Some(is_valid)),
                        Err(_) => return (idx, None),
                    }
                }
            }

            (idx, None)
        });
    }

    // Collect results
    while let Some(result) = tasks.join_next().await {
        if let Ok((idx, validated)) = result {
            secrets[idx].validated = validated;
        }
    }

    Ok(())
}