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