1#![allow(clippy::too_many_arguments)]
6#![allow(clippy::type_complexity)]
7
8pub mod reqwest {
10 pub use reqwest::*;
11}
12
13pub mod cache;
15pub mod domain_allowlist;
16pub mod interpolate;
17pub mod oob;
18pub mod rate_limit;
19mod ssrf;
20mod verify;
21
22use std::collections::HashMap;
23use std::sync::Arc;
24use std::time::Duration;
25
26use dashmap::DashMap;
27use keyhog_core::{redact, DedupedMatch, DetectorSpec, VerificationResult, VerifiedFinding};
28
29use crate::reqwest::{Client, Error as ReqwestError};
32pub use keyhog_core::{dedup_matches, DedupScope};
33use thiserror::Error;
34use tokio::sync::{Notify, Semaphore};
35
36#[derive(Debug, Error)]
38pub enum VerifyError {
39 #[error(
40 "failed to send HTTP request: {0}. Fix: check network access, proxy settings, and the verification endpoint"
41 )]
42 Http(#[from] ReqwestError),
43 #[error(
44 "failed to build configured HTTP client: {0}. Fix: use a valid timeout and supported TLS/network configuration"
45 )]
46 ClientBuild(ReqwestError),
47 #[error(
48 "failed to resolve verification field: {0}. Fix: use `match` or `companion.<name>` fields that exist in the detector spec"
49 )]
50 FieldResolution(String),
51}
52
53pub struct VerificationEngine {
55 client: Client,
56 detectors: Arc<HashMap<Arc<str>, DetectorSpec>>,
57 service_semaphores: Arc<HashMap<Arc<str>, Arc<Semaphore>>>,
59 global_semaphore: Arc<Semaphore>,
61 timeout: Duration,
62 cache: Arc<cache::VerificationCache>,
64 pub(crate) inflight: Arc<DashMap<(Arc<str>, Arc<str>), Arc<Notify>>>,
68 pub(crate) max_inflight_keys: usize,
69 pub(crate) danger_allow_private_ips: bool,
70 pub(crate) danger_allow_http: bool,
71 pub(crate) oob_session: Option<Arc<oob::OobSession>>,
76}
77
78pub struct VerifyConfig {
80 pub timeout: Duration,
82 pub max_concurrent_per_service: usize,
84 pub max_concurrent_global: usize,
86 pub max_inflight_keys: usize,
88 pub danger_allow_private_ips: bool,
90 pub danger_allow_http: bool,
94}
95
96impl Default for VerifyConfig {
97 fn default() -> Self {
98 Self {
99 timeout: Duration::from_secs(5),
100 max_concurrent_per_service: 5,
101 max_concurrent_global: 20,
102 max_inflight_keys: 10_000,
103 danger_allow_private_ips: false,
104 danger_allow_http: false,
105 }
106 }
107}
108
109pub(crate) fn into_finding(
111 group: DedupedMatch,
112 verification: VerificationResult,
113 metadata: HashMap<String, String>,
114) -> VerifiedFinding {
115 VerifiedFinding {
116 detector_id: group.detector_id,
117 detector_name: group.detector_name,
118 service: group.service,
119 severity: group.severity,
120 credential_redacted: redact(&group.credential),
121 credential_hash: group.credential_hash,
122 location: group.primary_location,
123 verification,
124 metadata,
125 additional_locations: group.additional_locations,
126 confidence: group.confidence,
127 }
128}