#![warn(missing_docs)]
#![cfg_attr(
not(test),
deny(
clippy::unwrap_used,
clippy::expect_used,
clippy::todo,
clippy::unimplemented,
clippy::panic
)
)]
#![allow(
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::missing_errors_doc,
clippy::pedantic
)]
mod allowlist;
mod api;
pub mod ascii_ci;
mod aws;
mod config;
mod credential;
mod dedup;
mod detector_file_io;
mod display;
mod encoding;
mod finding;
pub mod git_lfs;
mod hardening;
mod hyperscan_cache;
pub mod json_selector;
mod report;
mod safe_bin;
mod source;
mod spec;
mod state_file;
pub mod timing;
pub mod verification_domain;
pub mod winpath;
use std::borrow::Cow;
pub use api::*;
mod auto_fix;
mod calibration;
mod merkle_index;
mod merkle_spec_hash;
mod rule_filter;
mod embedded {
include!(concat!(env!("OUT_DIR"), "/embedded_detectors.rs"));
}
pub(crate) fn embedded_detector_tomls() -> &'static [(&'static str, &'static str)] {
embedded::EMBEDDED_DETECTORS
}
#[inline]
pub fn embedded_detector_count() -> usize {
embedded_detector_tomls().len()
}
pub(crate) const STALE_TMP_CUTOFF_SECS: u64 = 60 * 60;
pub(crate) const KEYHOG_CACHE_SUBDIR: &str = "keyhog";
pub(crate) fn keyhog_cache_root() -> Option<std::path::PathBuf> {
dirs::cache_dir().map(|dir| dir.join(KEYHOG_CACHE_SUBDIR))
}
pub fn load_embedded_detectors_or_fail() -> Result<Vec<DetectorSpec>, SpecError> {
let embedded = embedded_detector_tomls();
let mut detectors = Vec::with_capacity(embedded.len());
let mut failed = Vec::new();
for (name, toml_content) in embedded {
match parse_embedded_detector(name, toml_content) {
Ok(detector) => detectors.push(detector),
Err(error) => failed.push(error),
}
}
if !failed.is_empty() {
let detail = failed
.iter()
.map(|error| format!(" - {error}"))
.collect::<Vec<_>>()
.join("\n");
return Err(SpecError::EmbeddedCorpusCorrupt {
failed_count: failed.len(),
total: embedded.len(),
detail,
});
}
Ok(detectors)
}
fn parse_embedded_detector(name: &str, toml_content: &str) -> Result<DetectorSpec, String> {
let file =
toml::from_str::<DetectorFile>(toml_content).map_err(|error| format!("{name}: {error}"))?;
let errors: Vec<String> = spec::validate_detector(&file.detector)
.into_iter()
.filter_map(|issue| match issue {
spec::QualityIssue::Error(error) => Some(error),
spec::QualityIssue::Warning(_) => None,
})
.collect();
if errors.is_empty() {
Ok(file.detector)
} else {
Err(format!(
"{name}: detector quality gate rejected the embedded spec: {}",
errors.join("; ")
))
}
}
pub fn embedded_detector_specs() -> &'static [DetectorSpec] {
static SPECS: std::sync::LazyLock<Vec<DetectorSpec>> =
std::sync::LazyLock::new(|| match load_embedded_detectors_or_fail() {
Ok(specs) => specs,
Err(error) => panic!(
"embedded detector corpus failed to load: {error}. The detector \
specifications live in the bundled TOMLs; refusing to run without them."
),
});
&SPECS
}
pub fn detector_spec_by_id(id: &str) -> Option<&'static DetectorSpec> {
static BY_ID: std::sync::LazyLock<
std::collections::HashMap<&'static str, &'static DetectorSpec>,
> = std::sync::LazyLock::new(|| {
embedded_detector_specs()
.iter()
.map(|spec| (spec.id.as_str(), spec))
.collect()
});
BY_ID.get(id).copied()
}
#[inline]
pub fn git_hash() -> &'static str {
env!("GIT_HASH")
}
#[inline]
pub fn detector_digest() -> &'static str {
env!("KEYHOG_DETECTOR_DIGEST")
}
pub fn redact(s: &str) -> Cow<'static, str> {
if s.is_ascii() {
if s.len() <= 8 {
return Cow::Borrowed("****");
}
let edge = redaction_edge_len(s.len());
let mut out = String::with_capacity((edge * 2) + 3);
out.push_str(&s[..edge]);
out.push_str("...");
out.push_str(&s[s.len() - edge..]);
return Cow::Owned(out);
}
let char_count = s.chars().count();
if char_count <= 8 {
return Cow::Borrowed("****");
}
let edge = redaction_edge_len(char_count);
let prefix: String = s.chars().take(edge).collect();
let suffix: String = s.chars().skip(char_count.saturating_sub(edge)).collect();
Cow::Owned(format!("{prefix}...{suffix}"))
}
fn redaction_edge_len(char_count: usize) -> usize {
(char_count / 8).clamp(1, 4)
}
#[doc(hidden)]
pub mod testing;