#![allow(clippy::too_many_arguments)]
#![allow(clippy::type_complexity)]
pub mod reqwest {
pub use reqwest::*;
}
pub mod cache;
pub mod domain_allowlist;
pub mod interpolate;
pub mod oob;
pub mod rate_limit;
mod ssrf;
mod verify;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use dashmap::DashMap;
use keyhog_core::{redact, DedupedMatch, DetectorSpec, VerificationResult, VerifiedFinding};
use crate::reqwest::{Client, Error as ReqwestError};
pub use keyhog_core::{dedup_matches, DedupScope};
use thiserror::Error;
use tokio::sync::{Notify, Semaphore};
#[derive(Debug, Error)]
pub enum VerifyError {
#[error(
"failed to send HTTP request: {0}. Fix: check network access, proxy settings, and the verification endpoint"
)]
Http(#[from] ReqwestError),
#[error(
"failed to build configured HTTP client: {0}. Fix: use a valid timeout and supported TLS/network configuration"
)]
ClientBuild(ReqwestError),
#[error(
"failed to resolve verification field: {0}. Fix: use `match` or `companion.<name>` fields that exist in the detector spec"
)]
FieldResolution(String),
}
pub struct VerificationEngine {
client: Client,
detectors: Arc<HashMap<Arc<str>, DetectorSpec>>,
service_semaphores: Arc<HashMap<Arc<str>, Arc<Semaphore>>>,
global_semaphore: Arc<Semaphore>,
timeout: Duration,
cache: Arc<cache::VerificationCache>,
pub(crate) inflight: Arc<DashMap<(Arc<str>, Arc<str>), Arc<Notify>>>,
pub(crate) max_inflight_keys: usize,
pub(crate) danger_allow_private_ips: bool,
pub(crate) danger_allow_http: bool,
pub(crate) oob_session: Option<Arc<oob::OobSession>>,
}
pub struct VerifyConfig {
pub timeout: Duration,
pub max_concurrent_per_service: usize,
pub max_concurrent_global: usize,
pub max_inflight_keys: usize,
pub danger_allow_private_ips: bool,
pub danger_allow_http: bool,
}
impl Default for VerifyConfig {
fn default() -> Self {
Self {
timeout: Duration::from_secs(5),
max_concurrent_per_service: 5,
max_concurrent_global: 20,
max_inflight_keys: 10_000,
danger_allow_private_ips: false,
danger_allow_http: false,
}
}
}
pub(crate) fn into_finding(
group: DedupedMatch,
verification: VerificationResult,
metadata: HashMap<String, String>,
) -> VerifiedFinding {
VerifiedFinding {
detector_id: group.detector_id,
detector_name: group.detector_name,
service: group.service,
severity: group.severity,
credential_redacted: redact(&group.credential),
credential_hash: group.credential_hash,
location: group.primary_location,
verification,
metadata,
additional_locations: group.additional_locations,
confidence: group.confidence,
}
}