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/// Offline AWS account-ID decode + canary-token classification (single source
32/// of truth shared by the scanner's finding metadata and the verifier's
33/// suppress-live-verification-for-canaries gate).
34pub mod aws;
35/// ANSI-colored CLI startup banner with detector counts.
36pub mod banner;
37/// Configuration system for KeyHog scanning options.
38pub mod config;
39/// Secure credential storage and redaction.
40pub mod credential;
41mod dedup;
42/// Shared standard Base64 decode (wire / K8s), bounded for DoS safety.
43pub mod encoding;
44mod finding;
45/// Security hardening: memory zeroization and process isolation helpers.
46pub mod hardening;
47/// Structured reporting (JSON, SARIF, Text).
48pub mod report;
49/// Safe absolute-path resolution for external binaries.
50pub mod safe_bin;
51mod source;
52mod spec;
53use std::borrow::Cow;
54
55/// Global registry for sources and verifiers.
56pub mod registry;
57
58pub use allowlist::*;
59pub use config::*;
60pub use credential::{Credential, SensitiveString};
61pub use dedup::*;
62pub use finding::*;
63pub use report::*;
64pub use source::*;
65/// Auto-fix suggestion logic for SARIF output.
66pub mod auto_fix;
67/// Bayesian confidence calibration for detectors.
68pub mod calibration;
69/// Incremental scan state via BLAKE3 Merkle index.
70pub mod merkle_index;
71mod merkle_spec_hash;
72pub use merkle_spec_hash::compute_spec_hash;
73/// Declarative `.keyhogignore.toml` rule-based finding suppression.
74/// Wraps vyre's CPU rule evaluator with a TOML schema scoped to
75/// keyhog's finding shape (detector / service / severity / path /
76/// credential_hash predicates).
77pub mod rule_filter;
78pub use rule_filter::{RuleSuppressor, RuleSuppressorError};
79pub use spec::*;
80
81// Embedded detectors compiled into the binary at build time.
82// These are used when no external detectors directory is found.
83mod embedded {
84 include!(concat!(env!("OUT_DIR"), "/embedded_detectors.rs"));
85}
86
87/// Load detectors from embedded data (compiled into the binary).
88/// Returns detector TOML strings that can be parsed by the spec loader.
89pub fn embedded_detector_tomls() -> &'static [(&'static str, &'static str)] {
90 embedded::EMBEDDED_DETECTORS
91}
92
93/// Number of embedded detector specs (authoritative for banners and tests).
94#[inline]
95pub fn embedded_detector_count() -> usize {
96 embedded_detector_tomls().len()
97}
98
99/// Redact a sensitive credential string for safe display.
100pub fn redact(s: &str) -> Cow<'static, str> {
101 // ASCII fast path: byte indexing is valid (no UTF-8 boundary risk),
102 // skips the O(n) `chars().count()` walk plus two intermediate `String`
103 // allocations from `take(4).collect()` / `skip(n).collect()`. Most
104 // credentials are pure ASCII (provider keys, hashes, base64 tokens).
105 if s.is_ascii() {
106 if s.len() <= 8 {
107 return Cow::Borrowed("****");
108 }
109 let mut out = String::with_capacity(s.len().min(11));
110 out.push_str(&s[..4]);
111 out.push_str("...");
112 out.push_str(&s[s.len() - 4..]);
113 return Cow::Owned(out);
114 }
115 // UTF-8 path: char-count for grapheme correctness.
116 let char_count = s.chars().count();
117 if char_count <= 8 {
118 return Cow::Borrowed("****");
119 }
120 let first_four: String = s.chars().take(4).collect();
121 let last_four: String = s.chars().skip(char_count.saturating_sub(4)).collect();
122 Cow::Owned(format!("{first_four}...{last_four}"))
123}