pub(super) fn has_secret_keyword_fast(data: &[u8]) -> bool {
use aho_corasick::AhoCorasick;
use std::sync::LazyLock;
static AC: LazyLock<Option<AhoCorasick>> = LazyLock::new(|| {
match AhoCorasick::new(
crate::secret_prefixes::multiline_secret_prefixes()
.iter()
.map(String::as_str),
) {
Ok(ac) => Some(ac),
Err(e) => {
crate::prefilter_degrade::warn_prefilter_disabled(
"multiline secret-keyword gate (has_secret_keyword_fast)",
&e,
);
None
}
}
});
AC.as_ref().is_none_or(|ac| ac.find(data).is_some())
}
#[cfg(any(feature = "entropy", test))]
pub(super) fn has_high_entropy_run_at_least(data: &[u8], min_run: usize) -> bool {
let min_run = min_run.max(1);
let mut run = 0usize;
for &b in data {
if is_entropy_candidate_byte(b) {
run += 1;
if run >= min_run {
return true;
}
} else {
run = 0;
}
}
false
}
#[cfg(any(feature = "entropy", test))]
fn is_entropy_candidate_byte(b: u8) -> bool {
b.is_ascii_alphanumeric()
|| matches!(
b,
b'-' | b'_'
| b'+'
| b'/'
| b'='
| b'.'
| b':'
| b'!'
| b'@'
| b'#'
| b'$'
| b'%'
| b'^'
| b'&'
| b'*'
)
}
pub(super) fn looks_like_variable_name(s: &str) -> bool {
let bytes = s.as_bytes();
if bytes.is_empty() || bytes.len() > 64 {
return false;
}
bytes
.iter()
.all(|&b| b.is_ascii_alphanumeric() || b == b'_')
}
pub(crate) fn resolve_value_shaped_group(
locs: ®ex::CaptureLocations,
text: &str,
group: usize,
groups_total: usize,
current: (usize, usize),
) -> (usize, usize) {
if !looks_like_variable_name(&text[current.0..current.1]) || groups_total <= 2 {
return current;
}
for g in 1..groups_total {
if g == group {
continue;
}
if let Some((s, e)) = locs.get(g) {
let candidate = &text[s..e];
if !looks_like_variable_name(candidate) && candidate.len() >= 8 {
return (s, e);
}
}
}
current
}
pub(crate) fn extend_known_prefix_credential<'a>(
data: &'a str,
credential: &'a str,
match_end: usize,
validate: impl Fn(&str, bool) -> crate::checksum::ChecksumConfidenceDecision,
) -> (&'a str, usize, crate::checksum::ChecksumConfidenceDecision) {
let original = credential;
let original_end = match_end;
let original_validation = validate(original, true);
let (credential, match_end) = if original_validation.claims_family()
|| crate::confidence::known_prefix_body(credential).is_some()
{
let bytes = data.as_bytes();
let mut end = match_end;
while end < bytes.len() && is_provider_token_byte(bytes[end]) {
end += 1;
}
if end == match_end || !data.is_char_boundary(end) {
(credential, match_end)
} else {
let cred_start = (credential.as_ptr() as usize).wrapping_sub(data.as_ptr() as usize);
if cred_start <= match_end && end <= bytes.len() && data.is_char_boundary(cred_start) {
(&data[cred_start..end], end)
} else {
(credential, match_end)
}
}
} else {
(credential, match_end)
};
let (credential, match_end) = extend_base64_padding(data, credential, match_end);
if credential.len() != original.len() {
let extended_validation = validate(credential, false);
if original_validation.is_proven_valid() && !extended_validation.is_proven_valid() {
return (original, original_end, original_validation);
}
return (credential, match_end, extended_validation);
}
(credential, match_end, original_validation)
}
fn extend_base64_padding<'a>(
data: &'a str,
credential: &'a str,
match_end: usize,
) -> (&'a str, usize) {
if !credential
.bytes()
.all(crate::decode::is_base64_candidate_byte)
{
return (credential, match_end);
}
let bytes = data.as_bytes();
let mut end = match_end;
let mut pad = 0u8;
while end < bytes.len() && bytes[end] == b'=' && pad < 2 {
end += 1;
pad += 1;
}
if pad > 0 && data.is_char_boundary(end) {
let cred_start = (credential.as_ptr() as usize).wrapping_sub(data.as_ptr() as usize);
if cred_start <= match_end && data.is_char_boundary(cred_start) {
(&data[cred_start..end], end)
} else {
(credential, match_end)
}
} else {
(credential, match_end)
}
}
fn is_provider_token_byte(byte: u8) -> bool {
byte.is_ascii_alphanumeric()
|| crate::engine::phase2_generic::keywords::is_assignment_compact_separator(byte)
}
pub(super) fn compute_pattern_signals(
entry: &crate::types::CompiledPattern,
execution_policy: &crate::detector_execution_policy::CompiledDetectorExecutionPolicy,
chunk: &keyhog_core::Chunk,
preprocessed: &crate::types::ScannerPreprocessedText<'_>,
) -> (bool, bool) {
let kw = entry.match_proves_keyword_nearby
|| execution_policy.keyword_nearby(chunk.data.as_bytes(), preprocessed.text.as_bytes());
let sf = chunk
.metadata
.path
.as_deref()
.map(crate::confidence::is_sensitive_path)
.unwrap_or(false); (kw, sf)
}
#[cfg(test)]
#[path = "../../tests/unit/engine_scan_filters.rs"]
mod tests;