use base64::Engine;
use keyhog_scanner::decode_structure::{
analyze, decoded_contains_placeholder, is_encoded_binary, looks_like_uniform_base64_blob,
DecodeStructure,
};
fn b64(bytes: &[u8]) -> String {
base64::engine::general_purpose::STANDARD.encode(bytes)
}
#[test]
fn png_blob_is_binary_payload() {
let mut png = b"\x89PNG\r\n\x1a\n".to_vec();
png.extend_from_slice(&[1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
let s = b64(&png);
let a = analyze(&s);
assert_eq!(a.magic, Some("png"));
assert!(a.is_binary_payload());
assert!(is_encoded_binary(&s));
}
#[test]
fn gzip_zlib_pdf_elf_are_binary_payloads() {
for (sig, name) in [
(&b"\x1f\x8b\x08\x00"[..], "gzip"),
(&b"\x78\x9c\x01\x02\x03\x04"[..], "zlib"),
(&b"%PDF-1.4\n%abc"[..], "pdf"),
(&b"\x7fELF\x02\x01\x01\x00"[..], "elf"),
] {
let mut blob = sig.to_vec();
blob.extend_from_slice(&[9u8; 24]);
let s = b64(&blob);
assert_eq!(analyze(&s).magic, Some(name), "magic for {name}");
assert!(is_encoded_binary(&s), "{name} must read as binary");
}
}
#[test]
fn real_protobuf_message_is_binary_payload() {
let msg = [
0x08, 0x96, 0x01, 0x12, 0x07, b't', b'e', b's', b't', b'i', b'n', b'g', 0x18, 0x01, 0x25, 0xef, 0xbe, 0xad, 0xde, ];
let s = b64(&msg);
assert!(analyze(&s).protobuf_wire, "valid protobuf must parse");
assert!(is_encoded_binary(&s));
}
#[test]
fn random_secret_is_not_binary_payload() {
let mut state: u64 = 0x9e37_79b9_7f4a_7c15;
let mut false_suppress = 0;
for _ in 0..3000 {
let raw: Vec<u8> = (0..36)
.map(|_| {
state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
(state >> 33) as u8
})
.collect();
let s = b64(&raw);
if is_encoded_binary(&s) {
false_suppress += 1;
}
}
assert!(
false_suppress <= 6,
"random secrets must not read as binary (got {false_suppress}/3000)"
);
}
#[test]
fn realistic_api_key_tokens_are_not_binary() {
let tokens = [
"ghp_aBcD1234EFgh5678ijkl9012MNop3456qrST",
"AKIAIOSFODNN7EXAMPLE0000",
"sk-proj-abcdefghijklmnopqrstuvwxyz0123456789ABCD",
"xoxb-1234567890-1234567890123-AbCdEfGhIjKlMnOpQrStUvWx",
];
for t in tokens {
assert!(!is_encoded_binary(t), "token {t} must not read as binary");
}
}
#[test]
fn short_candidates_are_skipped() {
assert!(!is_encoded_binary("short"));
assert_eq!(analyze("short"), DecodeStructure::default());
}
#[test]
fn non_encoded_text_is_not_decodable() {
let a = analyze("this is a normal sentence with spaces!!");
assert!(!a.decodable);
assert!(!a.is_binary_payload());
}
#[test]
fn base64_wrapping_aws_example_credential_is_caught() {
assert!(decoded_contains_placeholder("QUtJQUVYQU1QTEVFWEFNUExFMTI="));
}
#[test]
fn base64_wrapping_stripe_placeholder_is_caught() {
let s = base64::engine::general_purpose::STANDARD.encode("sk_live_PLACEHOLDER_NOT_A_REAL_KEY");
assert!(decoded_contains_placeholder(&s));
}
#[test]
fn base64_of_real_random_secret_passes() {
let s = base64::engine::general_purpose::STANDARD.encode(b"random_24_byte_secret_aBc");
assert!(!decoded_contains_placeholder(&s));
}
#[test]
fn short_credentials_skip_decode() {
assert!(!decoded_contains_placeholder("short"));
assert!(!decoded_contains_placeholder("AKIA"));
}
#[test]
fn pure_base64_blob_60_plus_chars_with_punct_matches() {
let s = "ABCDEFghij+/klmn0123abcdefghijklmnop+/qrstuvwxyz0123456789ABCD==";
assert_eq!(s.len(), 64);
assert!(looks_like_uniform_base64_blob(s));
}
#[test]
fn aws_secret_access_key_shape_passes() {
let s = "wJalrXUtnFEMIK7MDENGbPxRfiCYEXAMPLEKEY12";
assert_eq!(s.len(), 40);
assert!(!looks_like_uniform_base64_blob(s));
}
#[test]
fn github_pat_shape_passes() {
let s = "ghp_AbCdEf1234567890ZyXwVu9876543210QqRr";
assert!(!looks_like_uniform_base64_blob(s));
}
#[test]
fn jwt_shape_passes() {
let s = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.AbCdEf";
assert!(!looks_like_uniform_base64_blob(s));
}
#[test]
fn short_base64_below_60_chars_passes() {
let s = "ABCDEFGHIJKLMNOPQRSTUVWX/+abcdefghijklmn"; assert_eq!(s.len(), 40);
assert!(!looks_like_uniform_base64_blob(s));
}