use std::sync::LazyLock;
use regex::Regex;
use super::patterns::Confidence;
pub(crate) const JS_SPACE_CLASS: &str =
r"\t\n\x0B\f\r \u{a0}\u{1680}\u{2000}-\u{200a}\u{2028}\u{2029}\u{202f}\u{205f}\u{3000}\u{feff}";
fn is_single_repeated_character(value: &str) -> bool {
let mut characters = value.chars();
let Some(first) = characters.next() else {
return false;
};
characters.all(|character| character == first) && value.chars().count() >= 2
}
pub(crate) fn looks_like_placeholder(value: &str) -> bool {
if value.contains("${") || value.contains("{{") {
return true;
}
if value.starts_with('<') && value.ends_with('>') {
return true;
}
value.encode_utf16().count() >= 4 && is_single_repeated_character(value)
}
pub(crate) fn confidence_by_length(value: &str, high_at: usize, medium_at: usize) -> Confidence {
let length = value.encode_utf16().count();
if length >= high_at {
return Confidence::High;
}
if length >= medium_at {
return Confidence::Medium;
}
Confidence::Low
}
static JWT_SHAPE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$")
.expect("a constant pattern compiles")
});
pub(crate) fn is_jwt_shaped(value: &str) -> bool {
JWT_SHAPE.is_match(value)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn interpolations_and_angle_brackets_are_placeholders() {
assert!(looks_like_placeholder("${DATABASE_PASSWORD}"));
assert!(looks_like_placeholder("{{secret}}"));
assert!(looks_like_placeholder("<your-key-here>"));
}
#[test]
fn a_run_of_one_character_is_a_placeholder() {
assert!(looks_like_placeholder("xxxxxxxx"));
assert!(looks_like_placeholder("********"));
assert!(looks_like_placeholder("...."));
}
#[test]
fn a_short_run_is_not_a_placeholder() {
assert!(!looks_like_placeholder("xxx"));
assert!(!looks_like_placeholder("xx"));
}
#[test]
fn a_real_value_is_not_a_placeholder() {
assert!(!looks_like_placeholder("hunter2hunter2"));
assert!(!looks_like_placeholder("AKIAIOSFODNN7EXAMPLE"));
assert!(!looks_like_placeholder(""));
}
#[test]
fn confidence_tiers_on_length() {
assert_eq!(
confidence_by_length("a".repeat(32).as_str(), 32, 20),
Confidence::High
);
assert_eq!(
confidence_by_length("a".repeat(20).as_str(), 32, 20),
Confidence::Medium
);
assert_eq!(
confidence_by_length("a".repeat(19).as_str(), 32, 20),
Confidence::Low
);
}
#[test]
fn only_a_base64_json_header_counts_as_a_jwt() {
assert!(is_jwt_shaped("eyJhbGciOi.eyJzdWIi.dozjgNryP4"));
assert!(!is_jwt_shaped("1.2.3"));
assert!(!is_jwt_shaped("my.host.name"));
assert!(!is_jwt_shaped("src.utils.helper"));
assert!(!is_jwt_shaped("abc.def.ghi"));
}
}