Skip to main content

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}