keyhog_core/lib.rs
1// Lint bars for keyhog-core.
2//
3// Hard floor (kept in `deny`): the *security* lints. Every panic-y shortcut in
4// production code is a real bug. These never relax.
5//
6// `missing_docs` is `warn` at the crate floor (Santh STANDARD.md). Debt-bucket
7// modules (spec, finding, registry, source, credential, hardening, calibration)
8// carry per-module `allow(missing_docs)` that names the debt explicitly; each
9// per-module allow is removed once that module is fully documented and the
10// warn fires at full strength for it.
11#![warn(missing_docs)]
12#![cfg_attr(
13 not(test),
14 deny(
15 clippy::unwrap_used,
16 clippy::expect_used,
17 clippy::todo,
18 clippy::unimplemented,
19 clippy::panic
20 )
21)]
22#![allow(
23 clippy::module_name_repetitions,
24 clippy::must_use_candidate,
25 clippy::missing_errors_doc,
26 clippy::pedantic
27)]
28
29//! Core types shared across all KeyHog crates.
30pub mod allowlist;
31/// ANSI-colored CLI startup banner with detector counts.
32pub mod banner;
33/// Configuration system for KeyHog scanning options.
34pub mod config;
35/// Secure credential storage and redaction.
36pub mod credential;
37mod dedup;
38/// Shared standard Base64 decode (wire / K8s), bounded for DoS safety.
39pub mod encoding;
40mod finding;
41/// Security hardening: memory zeroization and process isolation helpers.
42pub mod hardening;
43/// Structured reporting (JSON, SARIF, Text).
44pub mod report;
45/// Safe absolute-path resolution for external binaries.
46pub mod safe_bin;
47mod source;
48mod spec;
49use std::borrow::Cow;
50
51/// Global registry for sources and verifiers.
52pub mod registry;
53
54pub use allowlist::*;
55pub use config::*;
56pub use credential::{Credential, SensitiveString};
57pub use dedup::*;
58pub use finding::*;
59pub use report::*;
60pub use source::*;
61/// Auto-fix suggestion logic for SARIF output.
62pub mod auto_fix;
63/// Bayesian confidence calibration for detectors.
64pub mod calibration;
65/// Incremental scan state via BLAKE3 Merkle index.
66pub mod merkle_index;
67mod merkle_spec_hash;
68pub use merkle_spec_hash::compute_spec_hash;
69/// Declarative `.keyhogignore.toml` rule-based finding suppression.
70/// Wraps vyre's CPU rule evaluator with a TOML schema scoped to
71/// keyhog's finding shape (detector / service / severity / path /
72/// credential_hash predicates).
73pub mod rule_filter;
74pub use rule_filter::{RuleSuppressor, RuleSuppressorError};
75pub use spec::*;
76
77// Embedded detectors compiled into the binary at build time.
78// These are used when no external detectors directory is found.
79mod embedded {
80 include!(concat!(env!("OUT_DIR"), "/embedded_detectors.rs"));
81}
82
83/// Load detectors from embedded data (compiled into the binary).
84/// Returns detector TOML strings that can be parsed by the spec loader.
85pub fn embedded_detector_tomls() -> &'static [(&'static str, &'static str)] {
86 embedded::EMBEDDED_DETECTORS
87}
88
89/// Number of embedded detector specs (authoritative for banners and tests).
90#[inline]
91pub fn embedded_detector_count() -> usize {
92 embedded_detector_tomls().len()
93}
94
95/// Redact a sensitive credential string for safe display.
96pub fn redact(s: &str) -> Cow<'static, str> {
97 // ASCII fast path: byte indexing is valid (no UTF-8 boundary risk),
98 // skips the O(n) `chars().count()` walk plus two intermediate `String`
99 // allocations from `take(4).collect()` / `skip(n).collect()`. Most
100 // credentials are pure ASCII (provider keys, hashes, base64 tokens).
101 if s.is_ascii() {
102 if s.len() <= 8 {
103 return Cow::Borrowed("****");
104 }
105 let mut out = String::with_capacity(s.len().min(11));
106 out.push_str(&s[..4]);
107 out.push_str("...");
108 out.push_str(&s[s.len() - 4..]);
109 return Cow::Owned(out);
110 }
111 // UTF-8 path: char-count for grapheme correctness.
112 let char_count = s.chars().count();
113 if char_count <= 8 {
114 return Cow::Borrowed("****");
115 }
116 let first_four: String = s.chars().take(4).collect();
117 let last_four: String = s.chars().skip(char_count.saturating_sub(4)).collect();
118 Cow::Owned(format!("{first_four}...{last_four}"))
119}