pub(crate) const GENERIC_PREFIX: &str = "generic-";
pub(crate) const ENTROPY_PREFIX: &str = "entropy-";
pub(crate) const REASSEMBLED_SUFFIX: &str = ":reassembled";
#[inline]
pub(crate) fn policy_detector_id(detector_id: &str) -> &str {
detector_id
.strip_suffix(REASSEMBLED_SUFFIX)
.unwrap_or(detector_id) }
pub(crate) const GENERIC_SECRET: &str = "generic-secret";
pub(crate) const GENERIC_KEYWORD_SECRET: &str = "generic-keyword-secret";
pub(crate) const GENERIC_API_KEY: &str = "generic-api-key";
#[cfg(test)]
pub(crate) const GENERIC_PASSWORD: &str = "generic-password";
pub(crate) const ENTROPY: &str = "entropy";
pub(crate) const PRIVATE_KEY: &str = "private-key";
pub(crate) const AWS_ACCESS_KEY: &str = "aws-access-key";
pub(crate) const GITHUB_CLASSIC_PAT: &str = "github-classic-pat";
pub(crate) const GITHUB_PAT_FINE_GRAINED: &str = "github-pat-fine-grained";
pub(crate) const GITLAB_PERSONAL_ACCESS_TOKEN: &str = "gitlab-personal-access-token";
pub(crate) const NPM_ACCESS_TOKEN: &str = "npm-access-token";
pub(crate) const PYPI_API_TOKEN: &str = "pypi-api-token";
pub(crate) const SLACK_BOT_TOKEN: &str = "slack-bot-token";
pub(crate) const STRIPE_SECRET_KEY: &str = "stripe-secret-key";
#[inline]
pub(crate) fn is_generic_detector(detector_id: &str) -> bool {
detector_id.starts_with(GENERIC_PREFIX)
}
#[inline]
pub(crate) fn is_entropy_detector(detector_id: &str) -> bool {
detector_id == ENTROPY || detector_id.starts_with(ENTROPY_PREFIX)
}
#[inline]
pub(crate) fn is_private_key_fallback(detector_id: &str) -> bool {
detector_id == PRIVATE_KEY
}
#[inline]
#[cfg(test)]
pub(crate) fn is_structural_password_slot_detector(detector_id: &str) -> bool {
keyhog_core::detector_spec_by_id(detector_id).is_some_and(|spec| spec.structural_password_slot)
}
#[inline]
pub(crate) fn is_generic_or_entropy_detector(detector_id: &str) -> bool {
is_generic_detector(detector_id) || is_entropy_detector(detector_id)
}
#[inline]
pub(crate) fn is_service_anchored_detector(detector_id: &str) -> bool {
keyhog_core::detector_spec_by_id(detector_id).map_or_else(
|| {
!is_generic_detector(detector_id)
&& !is_entropy_detector(detector_id)
&& !is_private_key_fallback(detector_id)
},
|detector| {
detector.kind != keyhog_core::DetectorKind::Phase2Generic && !detector.private_key_block
},
)
}
#[inline]
pub(crate) fn is_private_key_block_detector(detector_id: &str) -> bool {
keyhog_core::detector_spec_by_id(detector_id).is_some_and(|spec| spec.private_key_block)
}
#[cfg(test)]
mod detector_id_corpus_guard {
use super::*;
use crate::detector_catalog::bundled_detector_ids;
fn corpus_backed_consts() -> Vec<(&'static str, &'static str)> {
let v = vec![
("GENERIC_SECRET", GENERIC_SECRET),
("GENERIC_KEYWORD_SECRET", GENERIC_KEYWORD_SECRET),
("GENERIC_API_KEY", GENERIC_API_KEY),
("GENERIC_PASSWORD", GENERIC_PASSWORD),
("PRIVATE_KEY", PRIVATE_KEY),
("GITHUB_CLASSIC_PAT", GITHUB_CLASSIC_PAT),
("GITHUB_PAT_FINE_GRAINED", GITHUB_PAT_FINE_GRAINED),
("GITLAB_PERSONAL_ACCESS_TOKEN", GITLAB_PERSONAL_ACCESS_TOKEN),
("NPM_ACCESS_TOKEN", NPM_ACCESS_TOKEN),
("PYPI_API_TOKEN", PYPI_API_TOKEN),
("SLACK_BOT_TOKEN", SLACK_BOT_TOKEN),
("STRIPE_SECRET_KEY", STRIPE_SECRET_KEY),
];
v
}
fn synthetic_consts() -> Vec<(&'static str, &'static str)> {
vec![("ENTROPY", ENTROPY)]
}
#[test]
fn every_corpus_backed_const_names_a_real_embedded_detector() {
let corpus =
bundled_detector_ids().expect("embedded detector corpus must load fail-closed");
let missing: Vec<String> = corpus_backed_consts()
.into_iter()
.filter(|(_, id)| !corpus.contains(*id))
.map(|(name, id)| format!("{name} = {id:?}"))
.collect();
assert!(
missing.is_empty(),
"detector-id consts naming NO embedded detector (dead predicates): {missing:?}"
);
}
#[test]
fn stripe_secret_key_const_is_the_real_id_not_the_removed_phantom() {
let corpus = bundled_detector_ids().unwrap();
assert_eq!(STRIPE_SECRET_KEY, "stripe-secret-key");
assert!(corpus.contains("stripe-secret-key"));
assert!(
!corpus.contains("stripe-api-key"),
"the removed phantom id must not exist"
);
}
#[test]
fn renamed_checksum_label_consts_resolve_to_real_detectors() {
let corpus = bundled_detector_ids().unwrap();
assert_eq!(GITHUB_PAT_FINE_GRAINED, "github-pat-fine-grained");
assert!(corpus.contains(GITHUB_PAT_FINE_GRAINED));
assert!(!corpus.contains("github-fine-grained-pat"));
assert_eq!(GITLAB_PERSONAL_ACCESS_TOKEN, "gitlab-personal-access-token");
assert!(corpus.contains(GITLAB_PERSONAL_ACCESS_TOKEN));
assert!(!corpus.contains("gitlab-token"));
assert_eq!(SLACK_BOT_TOKEN, "slack-bot-token");
assert!(corpus.contains(SLACK_BOT_TOKEN));
assert!(!corpus.contains("slack-token"));
}
#[test]
fn synthetic_finding_ids_are_absent_from_the_toml_corpus() {
let corpus = bundled_detector_ids().unwrap();
for (name, id) in synthetic_consts() {
assert!(
!corpus.contains(id),
"{name} = {id:?} is a synthetic finding id; it must not collide with a TOML detector"
);
assert!(
id == ENTROPY || id.starts_with(ENTROPY_PREFIX),
"{name} = {id:?} must be an entropy-family synthetic id"
);
}
}
#[test]
fn family_prefixes_are_prefixes_not_detector_ids() {
let corpus = bundled_detector_ids().unwrap();
for (name, prefix) in [
("GENERIC_PREFIX", GENERIC_PREFIX),
("ENTROPY_PREFIX", ENTROPY_PREFIX),
] {
assert!(
prefix.ends_with('-'),
"{name} = {prefix:?} must end with '-'"
);
assert!(
!corpus.contains(prefix),
"{name} is a family prefix, not a detector id"
);
}
assert!(GENERIC_SECRET.starts_with(GENERIC_PREFIX));
assert!(GENERIC_API_KEY.starts_with(GENERIC_PREFIX));
assert!("entropy-generic".starts_with(ENTROPY_PREFIX));
}
#[test]
fn family_predicates_classify_the_real_ids_correctly() {
assert!(is_generic_detector(GENERIC_SECRET));
assert!(is_generic_detector(GENERIC_PASSWORD));
assert!(!is_generic_detector(GITHUB_CLASSIC_PAT));
assert!(!is_generic_detector(STRIPE_SECRET_KEY));
assert!(is_entropy_detector(ENTROPY));
assert!(is_entropy_detector("entropy-generic"));
assert!(!is_entropy_detector(GENERIC_SECRET));
assert!(!is_entropy_detector(GITHUB_CLASSIC_PAT));
assert!(is_service_anchored_detector(GITHUB_CLASSIC_PAT));
assert!(is_service_anchored_detector(STRIPE_SECRET_KEY));
assert!(is_service_anchored_detector(SLACK_BOT_TOKEN));
assert!(is_service_anchored_detector(GITLAB_PERSONAL_ACCESS_TOKEN));
assert!(!is_service_anchored_detector(GENERIC_SECRET));
assert!(!is_service_anchored_detector(ENTROPY));
assert!(!is_service_anchored_detector(PRIVATE_KEY));
assert!(is_service_anchored_detector("bearer-authorization"));
assert!(is_private_key_fallback(PRIVATE_KEY));
assert!(!is_private_key_fallback(GITHUB_CLASSIC_PAT));
assert!(!is_structural_password_slot_detector(GITHUB_CLASSIC_PAT));
assert!(!is_structural_password_slot_detector(GENERIC_SECRET));
}
#[test]
fn structural_password_slot_family_is_toml_declared() {
use std::collections::BTreeSet;
let specs = keyhog_core::load_embedded_detectors_or_fail().expect("embedded corpus loads");
let members: BTreeSet<&str> = specs
.iter()
.filter(|s| s.structural_password_slot)
.map(|s| s.id.as_str())
.collect();
let expected: BTreeSet<&str> = [
"bearer-authorization",
"cli-password-flag",
"sql-password",
"url-credentials",
]
.into_iter()
.collect();
assert_eq!(
members, expected,
"structural_password_slot TOML declarations drifted from the known family"
);
for id in &expected {
assert!(
is_structural_password_slot_detector(id),
"predicate must classify declared member `{id}` as a structural password slot"
);
}
let generic_password = specs
.iter()
.find(|spec| spec.id == "generic-password")
.expect("generic-password detector exists");
let structural_patterns: Vec<usize> = generic_password
.patterns
.iter()
.enumerate()
.filter_map(|(index, pattern)| pattern.structural_password_slot.then_some(index))
.collect();
assert_eq!(
structural_patterns,
vec![1],
"only the semicolon-delimited ODBC pattern proves a structural slot"
);
}
#[test]
fn weak_anchor_family_is_toml_declared() {
use std::collections::BTreeSet;
let specs = keyhog_core::load_embedded_detectors_or_fail().expect("embedded corpus loads");
let members: BTreeSet<&str> = specs
.iter()
.filter(|s| s.weak_anchor)
.map(|s| s.id.as_str())
.collect();
let expected: BTreeSet<&str> = [
"activecampaign-api-key",
"adobe-api-key",
"aerisweather-api-credentials",
"alchemy-api-key",
"azure-openai-api-key",
"bamboohr-api-key",
"base-api-credentials",
"calendly-api-key",
"census-api-key",
"chef-automate-token",
"crowdin-api-token",
"etherscan-api-key",
"flickr-api-key",
"foundation-api-key",
"getresponse-api-key",
"github-oauth-secret",
"rudder-api-token",
"sonarcloud-token",
"spotify-client-credentials",
"workato-api-credentials",
]
.into_iter()
.collect();
assert_eq!(
members, expected,
"weak_anchor TOML declarations drifted from the known family"
);
}
#[test]
fn private_key_block_family_is_toml_declared() {
use std::collections::BTreeSet;
let specs = keyhog_core::load_embedded_detectors_or_fail().expect("embedded corpus loads");
let members: BTreeSet<&str> = specs
.iter()
.filter(|s| s.private_key_block)
.map(|s| s.id.as_str())
.collect();
let expected: BTreeSet<&str> = [
"github-app-private-key",
"google-artifact-registry-key",
"private-key",
"ssh-private-key",
]
.into_iter()
.collect();
assert_eq!(
members, expected,
"private_key_block TOML declarations drifted from the known family"
);
for id in &expected {
assert!(
is_private_key_block_detector(id),
"predicate must classify declared member `{id}` as a private-key block"
);
}
}
}