use crate::args::{CliDedupScope, ScanArgs};
use anyhow::Result;
use keyhog_scanner::ScannerConfig;
use std::path::PathBuf;
mod calibration;
mod detectors;
mod effective;
mod engine_runtime;
mod policy;
mod runtime;
mod scanner;
use calibration::load_explicit_scan_calibration;
pub(crate) use detectors::{
auto_discover_detectors, detector_compile_failed, load_detectors_no_cache,
load_detectors_or_embedded, load_detectors_with_cache, validate_explicit_detector_path,
};
pub(crate) use effective::{autoroute_config_digest, render_effective_config};
pub(crate) use engine_runtime::ResolvedEngineRuntimeSettings;
pub(crate) use policy::{ResolvedAllowlistConfig, ResolvedReportPolicy, ResolvedVerifyPolicy};
#[cfg(feature = "git")]
pub(crate) use runtime::MAX_COMMITS_DEFAULT;
pub(crate) use runtime::{
backend_override_cli_value, backend_override_label, configure_hyperscan_cache_dir,
configure_threads, fused_depth_default, gpu_runtime_policy_for_backend_override,
gpu_runtime_policy_from_args, parse_backend_override, ScanRuntimeInput, FUSED_BATCH_DEFAULT,
MAX_THREADS_CAP, ML_THRESHOLD_DEFAULT, VERIFY_MAX_CONCURRENT_DEFAULT,
VERIFY_TIMEOUT_DEFAULT_SECS,
};
pub(crate) use scanner::build_scanner_config;
use scanner::{build_scanner_config_from_input, ScannerConfigInput};
#[derive(Debug, Clone)]
pub(crate) struct ResolvedScanConfig {
pub(crate) backend_override: Option<keyhog_scanner::ScanBackend>,
pub(crate) batch_pipeline: bool,
pub(crate) threads: Option<usize>,
pub(crate) reader_threads: Option<usize>,
pub(crate) fused_batch: usize,
pub(crate) fused_depth: Option<usize>,
pub(crate) gpu_runtime_policy: keyhog_scanner::gpu::GpuRuntimePolicy,
pub(crate) autoroute_gpu: bool,
pub(crate) autoroute_calibration: bool,
pub(crate) scanner: ScannerConfig,
pub(crate) min_confidence: f64,
pub(crate) ml_enabled: bool,
pub(crate) detector_min_confidence: std::collections::HashMap<String, f64>,
pub(crate) disabled_detectors: std::collections::HashSet<String>,
pub(crate) require_lockdown: bool,
pub(crate) regex_dfa_limit: Option<usize>,
pub(crate) gpu_batch_input_limit: Option<usize>,
pub(crate) max_file_size: Option<usize>,
#[cfg(feature = "git")]
pub(crate) max_commits: usize,
pub(crate) no_default_excludes: bool,
pub(crate) exclude_paths: Vec<String>,
pub(crate) incremental: bool,
pub(crate) incremental_cache_path: Option<PathBuf>,
pub(crate) hyperscan_cache_dir: Option<PathBuf>,
pub(crate) autoroute_cache_path: Option<PathBuf>,
pub(crate) calibration_cache_path: Option<PathBuf>,
pub(crate) calibration_entry_count: usize,
pub(crate) calibration_digest: u64,
pub(crate) aws_canary_accounts: Vec<String>,
pub(crate) scanner_tuning: keyhog_scanner::ScannerTuningConfig,
pub(crate) allowlist: ResolvedAllowlistConfig,
pub(crate) source_limits: keyhog_sources::SourceLimits,
pub(crate) report: ResolvedReportPolicy,
pub(crate) verify: ResolvedVerifyPolicy,
}
pub(crate) fn resolve_scan_config(args: &mut ScanArgs) -> Result<ResolvedScanConfig> {
let outcome = crate::config::apply_config_file(args);
if !outcome.config_errors.is_empty() {
anyhow::bail!(
"invalid .keyhog.toml configuration:\n{}",
outcome.config_errors.join("\n")
);
}
keyhog_core::set_extra_trusted_dirs(outcome.trusted_bin_dirs.clone());
let mut aws_canary_accounts = outcome.aws_canary_accounts;
aws_canary_accounts.sort();
aws_canary_accounts.dedup();
let aws_canary_set = aws_canary_accounts.iter().cloned().collect();
keyhog_core::set_extra_canary_accounts(aws_canary_set);
let runtime_input = ScanRuntimeInput::from_scan_args(args);
let report = ResolvedReportPolicy::from_scan_args(args);
let verify = ResolvedVerifyPolicy::from_scan_args(args);
configure_hyperscan_cache_dir(runtime_input.cache_dir.clone())?;
let autoroute_cache_path = crate::autoroute_cache_path::resolve_autoroute_cache_path(
runtime_input.autoroute_cache.as_deref(),
)
.map_err(anyhow::Error::msg)?;
let backend_override = parse_backend_override(runtime_input.backend.as_deref())?;
let scanner_tuning = outcome.scanner_tuning;
let scanner_input = ScannerConfigInput::from_scan_args(args);
let mut scanner = build_scanner_config_from_input(&scanner_input);
let (calibration_cache_path, calibration_store, calibration_entry_count, calibration_digest) =
load_explicit_scan_calibration(runtime_input.calibration_cache.as_deref())?;
if let Some(calibration_store) = calibration_store {
scanner = scanner.with_calibration(calibration_store);
}
let min_confidence = scanner.min_confidence;
let ml_enabled = scanner.ml_enabled;
Ok(ResolvedScanConfig {
backend_override,
batch_pipeline: runtime_input.batch_pipeline,
threads: runtime_input.threads,
reader_threads: runtime_input.reader_threads,
fused_batch: runtime_input.fused_batch,
fused_depth: runtime_input.fused_depth,
gpu_runtime_policy: runtime_input.gpu_runtime_policy,
autoroute_gpu: runtime_input.autoroute_gpu,
autoroute_calibration: runtime_input.autoroute_calibration,
scanner,
min_confidence,
ml_enabled,
detector_min_confidence: outcome.detector_min_confidence,
disabled_detectors: outcome.disabled_detectors.into_iter().collect(),
require_lockdown: outcome.require_lockdown,
regex_dfa_limit: runtime_input.regex_dfa_limit,
gpu_batch_input_limit: runtime_input.gpu_batch_input_limit,
max_file_size: runtime_input.max_file_size,
#[cfg(feature = "git")]
max_commits: runtime_input.max_commits,
no_default_excludes: runtime_input.no_default_excludes,
exclude_paths: runtime_input.exclude_paths,
incremental: runtime_input.incremental,
incremental_cache_path: runtime_input.incremental_cache_path,
hyperscan_cache_dir: runtime_input.cache_dir,
autoroute_cache_path,
calibration_cache_path,
calibration_entry_count,
calibration_digest,
aws_canary_accounts,
scanner_tuning,
allowlist: ResolvedAllowlistConfig {
file: outcome.allowlist_file,
require_reason: outcome.allowlist_require_reason,
require_approved_by: outcome.allowlist_require_approved_by,
max_expires_days: outcome.allowlist_max_expires_days,
},
source_limits: runtime_input.source_limits,
report,
verify,
})
}
pub(crate) fn resolved_scan_config_for_scanner(scanner: ScannerConfig) -> ResolvedScanConfig {
let min_confidence = scanner.min_confidence;
let ml_enabled = scanner.ml_enabled;
ResolvedScanConfig {
backend_override: None,
batch_pipeline: false,
threads: None,
reader_threads: None,
fused_batch: FUSED_BATCH_DEFAULT,
fused_depth: None,
gpu_runtime_policy: keyhog_scanner::gpu::GpuRuntimePolicy::Auto,
autoroute_gpu: false,
autoroute_calibration: false,
scanner,
min_confidence,
ml_enabled,
detector_min_confidence: std::collections::HashMap::new(),
disabled_detectors: std::collections::HashSet::new(),
require_lockdown: false,
regex_dfa_limit: None,
gpu_batch_input_limit: None,
max_file_size: None,
#[cfg(feature = "git")]
max_commits: MAX_COMMITS_DEFAULT,
no_default_excludes: false,
exclude_paths: Vec::new(),
incremental: false,
incremental_cache_path: None,
hyperscan_cache_dir: None,
autoroute_cache_path: None,
calibration_cache_path: None,
calibration_entry_count: 0,
calibration_digest: 0,
aws_canary_accounts: Vec::new(),
scanner_tuning: keyhog_scanner::ScannerTuningConfig::default(),
allowlist: ResolvedAllowlistConfig {
file: None,
require_reason: false,
require_approved_by: false,
max_expires_days: None,
},
source_limits: keyhog_sources::SourceLimits::default(),
report: ResolvedReportPolicy {
format: crate::args::OutputFormat::Text,
severity: None,
dedup: CliDedupScope::Credential,
verify: false,
lockdown: false,
show_secrets: false,
no_suppress_test_fixtures: false,
hide_client_safe: false,
},
verify: ResolvedVerifyPolicy::disabled(),
}
}
#[doc(hidden)]
pub(crate) mod testing {
use anyhow::Result;
use keyhog_core::DetectorSpec;
use std::path::Path;
pub(crate) fn sanitise_thread_count(
requested: usize,
physical_cores: usize,
source: &'static str,
) -> usize {
super::runtime::testing::sanitise_thread_count(requested, physical_cores, source)
}
pub(crate) fn load_detectors_from_dir_with_cache(
source_dir: &Path,
cache_path: &Path,
) -> Result<Vec<DetectorSpec>> {
super::detectors::testing::load_detectors_from_dir_with_cache(source_dir, cache_path)
}
}