keyhog-scanner 0.5.84

keyhog-scanner: high-performance SIMD-accelerated secret detection engine
use super::*;

/// Intent pin for the `CREDENTIAL_KEYWORD_NEEDLES` unification: `passwd`
/// is a canonical credential keyword (same set the entropy keyword list and
/// the TS non-null identifier gate use), so a camel-cased dotted candidate
/// carrying a `passwd` segment IS a source identifier and must suppress.
/// If someone narrows the canonical set and drops `passwd`, this fails.
#[test]
fn dotted_source_identifier_suppresses_camel_passwd_segment() {
    assert!(
        looks_like_dotted_source_identifier("userDb.passwd.value"),
        "camel-cased dotted candidate with a passwd segment must be a source identifier",
    );
}

/// Guardrail on the widening: `passwd` alone (no camel segment, no known
/// receiver) must NOT suppress, so the passwd inclusion does not silently
/// swallow flat dotted credential paths.
#[test]
fn dotted_passwd_without_camel_or_receiver_does_not_suppress() {
    assert!(
        !looks_like_dotted_source_identifier("db.passwd.field"),
        "a flat passwd dotted path with no camel segment must not be suppressed",
    );
}

/// Single-pass rewrite must preserve the 2..=5 segment-count window and the
/// empty-segment / non-alnum rejections.
#[test]
fn dotted_source_identifier_segment_count_and_body_bounds() {
    // 6 dotted segments is over the 5-segment ceiling → not an identifier.
    assert!(!looks_like_dotted_source_identifier("aB.cD.eF.gH.iJ.kL"));
    // Single segment (no dot) is under the floor.
    assert!(!looks_like_dotted_source_identifier("userDbPasswd"));
    // Empty segment rejects regardless of count.
    assert!(!looks_like_dotted_source_identifier("userDb..passwd"));
    // Non-alnum body byte rejects.
    assert!(!looks_like_dotted_source_identifier("userDb.pass-wd.value"));
}

/// First-segment receiver match short-circuits to true without needing a
/// camel/credential segment (the `first` capture in the single pass).
#[test]
fn dotted_source_identifier_receiver_first_segment() {
    assert!(looks_like_dotted_source_identifier(&format!(
        "{}.field",
        SOURCE_RECEIVERS[0].as_str()
    )));
}

/// A dotted lowercase snake_case path is a config key or a field access, not a
/// credential: the self-scan surfaced `plausibility.reject_repeated_blocks`
/// from a documentation table and `entropy_match.value` from scanner source as
/// entropy findings, because the gate demanded a camelCase segment that
/// snake_case code and TOML keys never carry. Every member of the shape is
/// covered, not the one that was reported: a two-segment key, a long
/// multi-word key, a key whose last segment carries a credential word, and a
/// field access behind a reference sigil.
#[test]
fn dotted_snake_case_config_key_is_a_source_identifier() {
    for value in [
        "plausibility.mixed_alnum_floor",
        "plausibility.reject_repeated_blocks",
        "plausibility.second_half_entropy_floor",
        "plausibility.allow_alphabetic_credential",
        "entropy_match.value",
        "&entropy_match.value",
        "*self.detector_id",
        "plausibility.leading_slash_base64_min_len",
        "checksum.sha256_floor",
        "config.detector.min_confidence",
    ] {
        assert!(
            looks_like_dotted_source_identifier(value),
            "{value} is a snake_case identifier path, not a credential",
        );
    }
}

/// The snake_case widening must not swallow dotted values that carry real
/// credential entropy: bare lowercase word paths stay visible (the
/// `db.passwd.field` contract above), and so does anything with digits, case
/// variation, or a non-identifier byte in a segment.
#[test]
fn dotted_snake_case_widening_keeps_credential_shapes_visible() {
    for value in [
        "db.passwd.field",
        "sk_live.AbC9dEf",
        "api_key.7f3d9b2c1a",
        "prod_secret.aGVsbG8=",
        "&db.passwd",
    ] {
        assert!(
            !looks_like_dotted_source_identifier(value),
            "{value} must stay visible to the entropy path",
        );
    }
}

#[test]
fn generated_template_interpolation_prefix_is_source_syntax() {
    let randomness = TokenRandomness::for_candidate("__vlist$");
    assert!(looks_like_source_code_expression_with_randomness(
        "__vlist$",
        &randomness
    ));
    assert!(!looks_like_template_interpolation_prefix("realSecret$"));
}

#[test]
fn indexed_javascript_length_expression_is_source_syntax() {
    for value in [
        "_a8fe8b046732.length]))",
        "_715561396085.length]))",
        "$715561396085.length])",
    ] {
        let randomness = TokenRandomness::for_candidate(value);
        assert!(looks_like_source_code_expression_with_randomness(
            value,
            &randomness
        ));
    }
    for value in [
        "_a8fe8b046732.length",
        "opaque-key.length]))",
        "_a8fe8b046732.length]))suffix",
        "715561396085.length]))",
    ] {
        let randomness = TokenRandomness::for_candidate(value);
        assert!(!looks_like_source_code_expression_with_randomness(
            value,
            &randomness
        ));
    }
}