pub mod limits;
mod utils;
mod validation_body;
use kingfisher_core::ValidationOutcome;
pub mod http_validation;
pub mod revocation;
mod revoker;
pub use revoker::Revoker;
pub mod aws;
pub mod azure;
pub mod coinbase;
pub mod gcp;
pub mod jwt;
pub mod jdbc;
pub mod mongodb;
pub mod mysql;
pub mod postgres;
pub mod ethereum;
pub mod raw;
pub use utils::{find_closest_variable, process_captures};
pub use validation_body::{ValidationResponseBody, as_str, clone_as_string, from_string};
pub use http_validation::{
SSRF_BLOCKED_MESSAGE, SsrfBlockedError, build_request_builder, check_host_resolvable,
check_url_resolvable, generate_http_cache_key_parts, is_ssrf_safe_ip, parse_http_method,
process_headers, retry_multipart_request, retry_request, validate_response,
with_request_template_globals,
};
pub use raw::{RawValidationOutcome, required_vars as raw_required_vars, validate_raw};
#[expect(deprecated)]
pub use http_validation::check_url_resolvable_safe;
pub use aws::{
aws_key_to_account_number, generate_aws_cache_key, revoke_aws_access_key,
set_aws_skip_account_ids, set_aws_validation_concurrency, should_skip_aws_validation,
validate_aws_credentials, validate_aws_credentials_input,
};
use std::sync::{LazyLock, OnceLock};
use std::{
sync::Arc,
time::{Duration, Instant},
};
use crossbeam_skiplist::SkipMap;
pub static GLOBAL_USER_AGENT: LazyLock<String> = LazyLock::new(build_user_agent);
static USER_AGENT_SUFFIX: OnceLock<String> = OnceLock::new();
const BROWSER_USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
AppleWebKit/537.36 (KHTML, like Gecko) \
Chrome/140.0.0.0 Safari/537.36";
fn build_user_agent() -> String {
let base = format!("{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
if let Some(suffix) = USER_AGENT_SUFFIX.get() {
format!("{base} {suffix} {BROWSER_USER_AGENT}")
} else {
format!("{base} {BROWSER_USER_AGENT}")
}
}
pub fn set_user_agent_suffix<S: Into<String>>(suffix: Option<S>) {
if let Some(suffix) = suffix {
let trimmed = suffix.into().trim().to_string();
if trimmed.is_empty() {
return;
}
let _ = USER_AGENT_SUFFIX.set(trimmed);
}
}
pub const VALIDATION_CACHE_SECONDS: u64 = 1200;
pub type Cache = Arc<SkipMap<String, CachedResponse>>;
#[derive(Clone, Debug)]
pub struct CachedResponse {
pub body: ValidationResponseBody,
pub status: http::StatusCode,
pub is_valid: bool,
pub outcome: ValidationOutcome,
pub timestamp: Instant,
}
impl CachedResponse {
pub fn new(body: ValidationResponseBody, status: http::StatusCode, is_valid: bool) -> Self {
let outcome = ValidationOutcome::from_legacy(false, is_valid, status.as_u16());
Self { body, status, is_valid, outcome, timestamp: Instant::now() }
}
pub fn with_outcome(mut self, outcome: ValidationOutcome) -> Self {
self.outcome = outcome;
self
}
pub fn is_still_valid(&self, cache_duration: Duration) -> bool {
self.timestamp.elapsed() < cache_duration
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cached_response_expiry() {
let response = CachedResponse::new(from_string("test"), http::StatusCode::OK, true);
assert!(response.is_still_valid(Duration::from_secs(60)));
assert!(response.is_still_valid(Duration::from_secs(1)));
}
}
pub mod betterleaks;
pub mod grpc;
mod validator;
pub use validator::{ValidatedFinding, Validator, ValidatorBuilder};
mod result;
pub use result::{ValidationReason, ValidationResult};
pub mod credential_uri;
pub mod engine;
pub use engine::ValidationEngine;