keyhog_scanner/lib.rs
1//! KeyHog Scanner: A high-performance, multi-layered secret detection engine.
2//!
3//! This crate implements the core scanning logic, combining SIMD pre-filtering,
4//! Aho-Corasick literal matching, regex fallback, and ML-based confidence scoring.
5//!
6//! # Module map (by pipeline stage)
7//!
8//! The modules below are declared in dependency order, but they READ in pipeline
9//! order, the same bytes→finding flow as [`docs/src/architecture.md`] and the
10//! method-level map in [`engine`] (`engine::mod` "# The one flow"). To find a
11//! responsibility, locate its stage:
12//!
13//! - **Config / state / shared types**: [`scanner_config`], [`scan_state`],
14//! [`types`], [`hw_probe`] (hardware routing), [`error`].
15//! - **Phase 1 · prefilter** (cheap "could a detector fire here?")
16//! [`alphabet_filter`], [`bigram_bloom`], [`prefix_trie`], `ascii_ci`,
17//! `simd` / `simdsieve_prefilter` (feature-gated), `prefilter_degrade`
18//! (loud Law-10 fallback).
19//! - **Compile and lifecycle** (detectors → matchers): `compiled_scanner/`,
20//! [`compiler`], `shared_regexes`, [`static_intern`].
21//! - **Scan engine** (phase 1 triggers + phase 2 extraction; CPU or GPU):
22//! [`engine`] (start at its header doc), [`pipeline`], [`gpu`]. Public scan
23//! entry methods live in `compiled_scanner/runtime.rs` and dispatch here.
24//! - **Decode-through** (nested base64/hex/url/unicode, recursive)
25//! [`decode`], [`decode_structure`].
26//! - **Entropy**: [`entropy`] is now the single home for all of it: the
27//! keyword/scanner detection logic plus the fast Shannon-entropy primitive
28//! `entropy::fast` (+ `entropy::avx512` / `entropy::fast_x86` /
29//! `entropy::fast_neon` SIMD impls, arch-gated).
30//! - **Confidence / ML**: [`ml_scorer`] (serves the embedded `weights.bin`;
31//! trained out-of-band by the repo's `ml/`), [`confidence`],
32//! `probabilistic_gate`.
33//! - **Context, fragment reassembly, multiline, suppression, resolution**
34//! [`context`], `fragment_cache`, [`multiline`], `suppression`,
35//! [`resolution`], `structured`.
36//! - **Specialized validators**: [`checksum`], [`jwt`], [`aws`],
37//! `homoglyph`, [`unicode_hardening`].
38//! - **Cross-cutting**: `platform_compat`, `placeholder_words`, [`telemetry`],
39//! `util_hash`.
40//!
41//! Most single-file modules are one responsibility each; the multi-file engine
42//! is the exception and carries its own internal map in `engine::mod`.
43
44#![deny(unsafe_op_in_unsafe_fn)]
45#![allow(clippy::too_many_arguments)]
46
47use std::borrow::Cow;
48
49#[cfg(test)]
50extern crate self as keyhog_scanner;
51
52#[cfg(test)]
53#[path = "../tests/adversarial/mod.rs"]
54mod adversarial;
55#[cfg(test)]
56#[path = "../tests/gap/mod.rs"]
57mod gap;
58#[cfg(test)]
59#[path = "../tests/gate/mod.rs"]
60mod gate;
61#[cfg(test)]
62#[path = "../tests/property/mod.rs"]
63mod property;
64#[cfg(test)]
65#[path = "../tests/support/mod.rs"]
66mod support;
67#[cfg(test)]
68#[path = "../tests/unit/mod.rs"]
69mod unit;
70
71// ── Public API ──────────────────────────────────────────────────────
72pub(crate) mod api;
73/// Compiled detector-owned assignment-key admission index.
74mod assignment_keyword_matcher;
75/// Tier-B generic credential-assignment keyword vocabulary (phase-2 prefilter).
76pub(crate) mod assignment_keywords;
77/// Offline AWS account-ID recovery from an access-key ID (no network/verify).
78pub mod aws;
79/// Service-specific credential checksum validation (GitHub, npm, Slack, etc.).
80pub mod checksum;
81/// Compiled scanner construction and lifecycle implementation.
82mod compiled_scanner;
83/// Detector compilation into high-performance matching structures.
84pub(crate) mod compiler;
85/// Heuristic and ML-based confidence scoring for candidate matches.
86pub(crate) mod confidence;
87/// Code context analysis (comments, assignments, test files).
88pub mod context;
89pub(crate) mod credential_context_keywords;
90pub(crate) mod credential_shapes;
91pub(crate) mod deadline;
92/// Decode-through pipeline for nested encodings (base64, hex, URL, etc.).
93pub mod decode;
94/// Decode-structure analysis: classify what a candidate base64/hex-decodes to
95/// (binary asset magic bytes, protobuf wire) so decode-through feeds scoring.
96pub(crate) mod decode_structure;
97pub(crate) mod detector_catalog;
98/// Cache-local detector facts used by candidate execution and emission.
99pub(crate) mod detector_execution_policy;
100/// Canonical detector-id strings and scanner-side detector-family predicates.
101pub(crate) mod detector_ids;
102/// Compiled canonical/decoded key-material policy from detector TOMLs.
103pub(crate) mod detector_key_material_policy;
104/// Compiled cache-local form of detector-owned model policy.
105#[cfg(feature = "ml")]
106pub(crate) mod detector_ml_policy;
107/// Unified detector-indexed runtime plan compiled from detector TOMLs.
108pub(crate) mod detector_plan;
109/// Core scan execution engine.
110pub(crate) mod engine;
111/// Shannon entropy analysis for secret detection.
112pub mod entropy;
113/// Tier-B per-family generic-detector entropy-floor calibration table.
114/// Specialized error types for the scanner.
115pub(crate) mod error;
116/// Cross-chunk fragment reassembly cache.
117pub(crate) mod fragment_cache;
118/// Detector-owned generic assignment value-shape adjudication.
119mod generic_assignment_shape;
120/// Named-detector ownership for assignment-key fallback suppression.
121pub(crate) mod generic_keyword_owner;
122/// GPU-accelerated matching via wgpu.
123pub mod gpu;
124/// Scanner GPU batch input policy.
125pub(crate) mod gpu_input_budget;
126/// GPU literal artifact compilation from the typed detector plan.
127pub(crate) mod gpu_literal_artifacts;
128/// Persistent GPU matcher artifact cache.
129pub(crate) mod gpu_matcher_cache;
130/// Hardware capability detection and backend selection.
131pub mod hw_probe;
132/// Machine learning inference for secret scoring.
133pub mod ml_scorer;
134/// Multiline secret reassembly logic.
135pub(crate) mod multiline;
136/// Pure phase-two regex truncation and UTF-8 focus boundaries.
137pub(crate) mod phase2_truncate;
138pub(crate) mod placeholder_words;
139pub(crate) mod platform_compat;
140/// Match resolution and deduplication.
141pub mod resolution;
142/// Process-wide scan profiling and diagnostics.
143pub(crate) mod scan_profile;
144/// Runtime match heap, interners, and ML pending queue for one scan.
145pub(crate) mod scan_state;
146/// Scanner configuration and state.
147pub(crate) mod scanner_config;
148/// Tier-B distinctive vendor secret-prefix vocabulary for the multiline no-hit gate.
149pub(crate) mod secret_prefixes;
150/// Coalesced match-to-input attribution primitive.
151pub(crate) mod segment_attribution;
152/// Static-string interner backed by a single-hash `ahash` map.
153/// Used by `CompiledScanner` to pre-intern detector metadata strings
154/// so the per-scan `ScanState` interner is hit only by dynamic
155/// strings (file paths, commit SHAs).
156pub(crate) mod static_intern;
157/// Shared types for the scanner engine.
158pub(crate) mod types;
159
160// Internal modules.
161pub(crate) mod adjudicate;
162/// SIMD-accelerated alphabet pre-filtering.
163pub(crate) mod alphabet_filter;
164pub(crate) mod anchored_regex;
165/// ASCII case-insensitive byte-search primitives shared by every hot path
166/// that needs to skim text without lowering the haystack first.
167pub(crate) mod ascii_ci;
168/// Bigram bloom filter for fast chunk gating.
169pub(crate) mod bigram_bloom;
170// The fast Shannon-entropy primitives (scalar dispatcher + AVX-512 / AVX2-SSE2 /
171// NEON SIMD impls) now live UNDER `entropy/` (entropy::fast / ::avx512 /
172// ::fast_x86 / ::fast_neon) (one home for all entropy code. See `entropy/mod.rs`).
173pub(crate) mod homoglyph;
174/// JWT structural validation and anomaly detection.
175pub mod jwt;
176/// Internal scan pipeline orchestration.
177pub(crate) mod pipeline;
178/// Prefix trie for efficient keyword propagation.
179pub(crate) mod prefix_trie;
180pub(crate) mod probabilistic_gate;
181pub(crate) mod structured;
182pub(crate) mod suppression;
183/// Per-scan telemetry: always-on counters + opt-in `--dogfood` events.
184pub mod telemetry;
185/// Shared parse + validate primitive for Tier-B single-column token lists
186/// (assignment keywords, multiline secret prefixes) (one owner, no drift).
187pub(crate) mod tier_b_list;
188pub(crate) mod tuning;
189/// Unicode normalization and homoglyph defense.
190pub(crate) mod unicode_hardening;
191/// Shared FNV-1a hash + content-keyed memoization primitives. Single home for
192/// the seed every per-scan cache keys on, plus the bounded thread-local cache
193/// helper they all share, so a hash change can never re-key only some caches.
194pub mod util_hash;
195
196/// Loud, recall-preserving degradation for static prefilter automata (Law 10).
197pub(crate) mod prefilter_degrade;
198
199pub(crate) use engine::floor_char_boundary;
200/// SHA-256 of a credential as the `CredentialHash` domain type. Re-exported
201/// from the single canonical implementation in `keyhog_core` so the scanner,
202/// core dedup, and telemetry all hash credentials identically (no second copy
203/// to drift). Hex encoding is a separate step at the serde/reporter boundary
204/// (`keyhog_core::hex_encode`), keeping the pre-dedup hot path zero-heap.
205pub(crate) use keyhog_core::sha256_hash;
206pub(crate) use pipeline::compute_line_offsets;
207
208#[cfg(feature = "simd")]
209pub(crate) mod simd;
210#[cfg(feature = "simdsieve")]
211mod simdsieve_prefilter;
212
213pub(crate) mod shared_regexes;
214
215pub use api::*;
216
217/// Configure the Hyperscan compiled-database cache directory for this process.
218///
219/// Call before compiling a scanner. `None` restores the platform default
220/// (`dirs::cache_dir()/keyhog`, with a per-user temp fallback). The SIMD backend
221/// still validates the final directory: explicit paths must live under the
222/// user's home or the per-uid keyhog temp cache root, must be user-owned, and
223/// must not be symlinks.
224#[cfg(feature = "simd")]
225pub fn set_hyperscan_cache_dir(path: Option<std::path::PathBuf>) {
226 simd::backend::set_configured_cache_dir(path);
227}
228
229/// Validate an explicit Hyperscan cache directory without compiling a scanner.
230#[cfg(feature = "simd")]
231pub fn validate_hyperscan_cache_dir(path: &std::path::Path) -> std::result::Result<(), String> {
232 simd::backend::validate_configured_cache_dir(path)
233}
234
235/// True when `detector_id` names the pure-entropy fallback family (`"entropy"`
236/// or any `"entropy-*"` id such as `entropy-token`).
237///
238/// Pure-entropy detectors fire on the Shannon entropy of the matched character
239/// run rather than on a distinctive prefix/shape, so whether one fires for a
240/// given secret is *context-dependent*: the same bytes embedded in a longer
241/// token run (a connection-string URL, a `key=` assignment) can dilute below the
242/// entropy gate even though they fire in isolation. Consumers that categorize a
243/// finding by detector family, and the contract test harness, which must not
244/// gate context-dependent firings all-or-nothing, use this to distinguish the
245/// entropy fallback from service-anchored detectors without re-encoding the
246/// naming contract owned by [`detector_ids`].
247#[inline]
248pub fn is_entropy_detector(detector_id: &str) -> bool {
249 detector_ids::is_entropy_detector(detector_id)
250}
251
252/// True for a detector that fires via the entropy / phase2-generic path (the
253/// `generic-*` family + entropy fallback), carrying ZERO patterns by design.
254#[inline]
255pub fn is_generic_or_entropy_detector(detector_id: &str) -> bool {
256 detector_ids::is_generic_or_entropy_detector(detector_id)
257}
258
259/// Strip invisible-reorder evasion characters (zero-width + RTL override, per
260/// [`unicode_hardening::is_evasion_char`]) from context-window text. Deliberately
261/// narrower than [`unicode_hardening::normalize_homoglyphs`]: this feeds the
262/// surrounding-context features, where collapsing homoglyphs/fullwidth/combining
263/// marks in ordinary prose would distort keyword and comment context; homoglyph
264/// folding stays on the credential-value scan path.
265pub(crate) fn normalize_chunk_data(data: &str) -> Cow<'_, str> {
266 if data.is_ascii() {
267 return Cow::Borrowed(data);
268 }
269 let mut normalized: Option<String> = None;
270 for (byte_pos, ch) in data.char_indices() {
271 if unicode_hardening::is_evasion_char(ch) {
272 normalized.get_or_insert_with(|| {
273 let mut out = String::with_capacity(data.len());
274 out.push_str(&data[..byte_pos]);
275 out
276 });
277 } else if let Some(out) = &mut normalized {
278 out.push(ch);
279 }
280 }
281 normalized.map(Cow::Owned).unwrap_or(Cow::Borrowed(data)) // LAW10: no evasion chars means the original scan text is byte-preserved.
282}
283
284#[doc(hidden)]
285pub mod testing;