pub(crate) fn is_known_example_credential(credential: &str) -> bool {
let bytes = credential.as_bytes();
if crate::ascii_ci::ends_with_ignore_ascii_case(bytes, b"EXAMPLE")
|| crate::ascii_ci::ends_with_ignore_ascii_case(bytes, b"EXAMPLEKEY")
{
return true;
}
let x_count = bytes.iter().filter(|&&b| b == b'x' || b == b'X').count();
if bytes.len() >= 16 && x_count > bytes.len() * 3 / 4 {
return true;
}
if is_hex_sequential_placeholder(credential) {
return true;
}
if is_empty_input_hash(credential) {
return true;
}
is_sequential_placeholder(credential)
}
fn is_empty_input_hash(credential: &str) -> bool {
let bytes = credential.as_bytes();
match bytes.len() {
32 => bytes.eq_ignore_ascii_case(b"d41d8cd98f00b204e9800998ecf8427e"), 40 => bytes.eq_ignore_ascii_case(b"da39a3ee5e6b4b0d3255bfef95601890afd80709"), 64 => bytes.eq_ignore_ascii_case(
b"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
), 128 => bytes.eq_ignore_ascii_case(
b"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce\
47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e",
), _ => false,
}
}
const SEQUENTIAL_STEP_RATIO_NUMERATOR: usize = 9;
const SEQUENTIAL_STEP_RATIO_DENOMINATOR: usize = 10;
fn sequential_step_threshold(step_count: usize) -> usize {
step_count * SEQUENTIAL_STEP_RATIO_NUMERATOR / SEQUENTIAL_STEP_RATIO_DENOMINATOR
}
pub(crate) fn is_sequential_placeholder(credential: &str) -> bool {
let body = credential_body_without_known_prefix(credential);
if body.len() < 8 {
return false;
}
let bytes = body.as_bytes();
if bytes.iter().all(|&byte| byte == bytes[0]) {
return true;
}
let pair = &bytes[..2];
bytes
.chunks(2)
.all(|chunk| chunk == pair || (chunk.len() < 2 && chunk[0] == pair[0]))
}
#[cfg(any(feature = "entropy", test))]
pub(crate) fn is_monotonic_sequence_placeholder(credential: &str) -> bool {
let body = credential_body_without_known_prefix(credential);
if body.len() < 8 {
return false;
}
let bytes = body.as_bytes();
let ascending = count_adjacent_byte_steps(bytes, ascii_forward_step);
let descending = count_adjacent_byte_steps(bytes, ascii_reverse_step);
let threshold = sequential_step_threshold(bytes.len().saturating_sub(1));
ascending > threshold || descending > threshold
}
#[cfg(any(feature = "entropy", test))]
fn ascii_forward_step(previous: u8, next: u8) -> bool {
next == previous.wrapping_add(1)
}
#[cfg(any(feature = "entropy", test))]
fn ascii_reverse_step(previous: u8, next: u8) -> bool {
previous == next.wrapping_add(1)
}
fn is_hex_sequential_placeholder(credential: &str) -> bool {
let body = credential_body_without_known_prefix(credential);
if body.len() < 16 || !body.bytes().all(|b| b.is_ascii_hexdigit()) {
return false;
}
let bytes = body.as_bytes();
let ascending = count_adjacent_byte_steps(bytes, hex_forward_step);
let descending = count_adjacent_byte_steps(bytes, hex_reverse_step);
let threshold = sequential_step_threshold(bytes.len() - 1);
if ascending > threshold || descending > threshold {
return true;
}
let pair_count = bytes.len() / 2;
if pair_count < 8 {
return false;
}
if hex_byte_values_are_sequential(bytes, pair_count) {
return true;
}
let ascending = count_pair_column_hex_steps(bytes, pair_count, 0);
let ascending2 = count_pair_column_hex_steps(bytes, pair_count, 1);
let threshold = sequential_step_threshold(pair_count - 1);
ascending > threshold && ascending2 > threshold
}
fn credential_body_without_known_prefix(credential: &str) -> &str {
crate::confidence::known_prefix_body(credential).unwrap_or(credential) }
fn count_adjacent_byte_steps(bytes: &[u8], step: fn(u8, u8) -> bool) -> usize {
bytes
.windows(2)
.filter(|window| step(window[0], window[1]))
.count()
}
fn count_pair_column_hex_steps(bytes: &[u8], pair_count: usize, column: usize) -> usize {
(1..pair_count)
.filter(|&pair| {
let previous = bytes[(pair - 1) * 2 + column];
let next = bytes[pair * 2 + column];
hex_pair_column_step(previous, next)
})
.count()
}
fn hex_byte_values_are_sequential(bytes: &[u8], pair_count: usize) -> bool {
let forward = count_pair_value_steps(bytes, pair_count, |previous, next| {
next == previous.wrapping_add(1)
});
let reverse = count_pair_value_steps(bytes, pair_count, |previous, next| {
previous == next.wrapping_add(1)
});
let threshold = sequential_step_threshold(pair_count - 1);
forward > threshold || reverse > threshold
}
fn count_pair_value_steps(bytes: &[u8], pair_count: usize, step: fn(u8, u8) -> bool) -> usize {
let Some(mut previous) = hex_pair_value(bytes, 0) else {
return 0;
};
let mut count = 0usize;
for pair in 1..pair_count {
let Some(next) = hex_pair_value(bytes, pair) else {
return 0;
};
if step(previous, next) {
count += 1;
}
previous = next;
}
count
}
fn hex_pair_value(bytes: &[u8], pair: usize) -> Option<u8> {
let hi = crate::decode::util::hex_val(bytes[pair * 2]).ok()?; let lo = crate::decode::util::hex_val(bytes[pair * 2 + 1]).ok()?; Some((hi << 4) | lo)
}
fn hex_forward_step(previous: u8, next: u8) -> bool {
let previous = previous.to_ascii_lowercase();
let next = next.to_ascii_lowercase();
next == previous + 1 || (previous == b'9' && next == b'a') || (previous == b'f' && next == b'0')
}
fn hex_reverse_step(previous: u8, next: u8) -> bool {
let previous = previous.to_ascii_lowercase();
let next = next.to_ascii_lowercase();
next + 1 == previous || (previous == b'a' && next == b'9') || (previous == b'0' && next == b'f')
}
fn hex_pair_column_step(previous: u8, next: u8) -> bool {
hex_forward_step(previous, next) || (previous == b'9' && next == b'0')
}
#[cfg(test)]
mod sequential_placeholder_tests {
use super::{is_known_example_credential, is_monotonic_sequence_placeholder};
#[test]
fn monotonic_runs_are_placeholders() {
for value in [
"12345678", "23456789", "abcdefgh", "87654321", "hgfedcba", "012345678", ] {
assert!(
is_monotonic_sequence_placeholder(value),
"expected {value:?} to be a monotonic-run placeholder"
);
}
}
#[test]
fn real_secrets_and_short_values_are_not_monotonic() {
for value in [
"aK9f2Lp7Qz", "1a2b3c4d5e", "1234567", "s3cr3tV4lue", "48293017", ] {
assert!(
!is_monotonic_sequence_placeholder(value),
"did NOT expect {value:?} to be flagged monotonic"
);
}
}
#[test]
fn monotonic_gate_scoped_out_of_universal_example_credential() {
assert!(is_monotonic_sequence_placeholder(
"abcdefghijklmnopqrstuvwx"
));
assert!(
!is_known_example_credential("abcdefghijklmnopqrstuvwx"),
"vendor-path example check must NOT suppress a sequential filler token"
);
assert!(is_known_example_credential("00000000"));
}
}
#[cfg(test)]
mod placeholder_suppression_adversarial_tests {
use super::{
is_empty_input_hash, is_hex_sequential_placeholder, is_known_example_credential,
is_sequential_placeholder, sequential_step_threshold,
};
#[test]
fn empty_input_hashes_of_every_length_are_recognized() {
assert!(is_empty_input_hash("d41d8cd98f00b204e9800998ecf8427e")); assert!(is_empty_input_hash(
"da39a3ee5e6b4b0d3255bfef95601890afd80709"
)); assert!(is_empty_input_hash(
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
)); assert!(is_empty_input_hash("D41D8CD98F00B204E9800998ECF8427E"));
}
#[test]
fn near_miss_digests_are_not_empty_input_hashes() {
assert!(!is_empty_input_hash("d41d8cd98f00b204e9800998ecf8427f"));
assert!(!is_empty_input_hash("d41d8cd98f00b204e9800998ecf842")); assert!(!is_empty_input_hash(
"prefix_d41d8cd98f00b204e9800998ecf8427e"
));
assert!(!is_empty_input_hash("")); }
#[test]
fn monotonic_hex_runs_are_placeholders() {
assert!(is_hex_sequential_placeholder("0123456789abcdef")); assert!(is_hex_sequential_placeholder("fedcba9876543210")); assert!(is_hex_sequential_placeholder(
"0123456789abcdef0123456789abcdef"
));
assert!(is_hex_sequential_placeholder("0123456789ABCDEF"));
}
#[test]
fn random_and_nonhex_bodies_are_not_hex_sequential() {
assert!(!is_hex_sequential_placeholder("deadbeefcafebabe")); assert!(!is_hex_sequential_placeholder("a3f8b2c9d1e07546")); assert!(!is_hex_sequential_placeholder("0123456789abcde")); assert!(!is_hex_sequential_placeholder("ghijklmnopqrstuv"));
}
#[test]
fn all_same_and_repeated_pair_bodies_are_placeholders() {
assert!(is_sequential_placeholder("aaaaaaaa")); assert!(is_sequential_placeholder("00000000"));
assert!(is_sequential_placeholder("abababab")); assert!(is_sequential_placeholder("=-=-=-=-")); }
#[test]
fn higher_period_and_short_bodies_are_not_sequential_placeholders() {
assert!(!is_sequential_placeholder("abcabcabc"));
assert!(!is_sequential_placeholder("aaaaaaa")); assert!(!is_sequential_placeholder("aK9f2Lp7Qz")); }
#[test]
fn sequential_step_threshold_is_exactly_nine_tenths_floored() {
assert_eq!(sequential_step_threshold(0), 0);
assert_eq!(sequential_step_threshold(7), 6); assert_eq!(sequential_step_threshold(10), 9);
assert_eq!(sequential_step_threshold(20), 18);
assert_eq!(sequential_step_threshold(100), 90);
}
#[test]
fn universal_example_gate_covers_every_arm() {
assert!(is_known_example_credential("MY_SECRET_KEY_EXAMPLE")); assert!(is_known_example_credential("service-api-EXAMPLEKEY")); assert!(is_known_example_credential("xxxxxxxxxxxxxxxx")); assert!(is_known_example_credential(
"d41d8cd98f00b204e9800998ecf8427e"
)); assert!(is_known_example_credential("0123456789abcdef")); assert!(is_known_example_credential("55555555")); }
#[test]
fn real_secrets_survive_the_universal_example_gate() {
assert!(!is_known_example_credential("aK9f2Lp7Qz3mN8bVxT1wR6yU"));
assert!(!is_known_example_credential(
"deadbeefcafebabe0feed1234567890a"
));
assert!(!is_known_example_credential("xoxb1a2b3c"));
}
}