keyhog 0.5.73

GPU-accelerated secret scanner for code, Git history, cloud, containers, browser assets, and live credential verification
//! E2E: generic decode-through works on a plain file and reports source offsets.

use crate::e2e::support::{scan_path, scan_text_file, write_temp_file};

#[test]
fn scan_plain_file_base64_decodes_secret_at_encoded_source_offset() {
    const SECRET: &str = "AKIAQYLPMN5HFIQR7XYA";
    const ENCODED_ASSIGNMENT: &str = "QVdTX0FDQ0VTU19LRVlfSUQ9QUtJQVFZTFBNTjVIRklRUjdYWUE=";
    let text = format!("base64_payload = \"{ENCODED_ASSIGNMENT}\"\n");
    let encoded_start = text
        .find(ENCODED_ASSIGNMENT)
        .expect("fixture contains encoded assignment");
    let encoded_end = encoded_start + ENCODED_ASSIGNMENT.len();

    let (stdout, stderr, code) = scan_text_file(&text, &[]);
    assert_eq!(
        code,
        Some(1),
        "plain base64 file must surface the decoded AWS key; stderr={stderr}; stdout={stdout}"
    );

    let findings = serde_json::from_str::<serde_json::Value>(&stdout)
        .expect("json")
        .as_array()
        .expect("array")
        .clone();
    // `core::redact` shows `<first edge>...<last edge>` where edge =
    // (len/8).clamp(1,4); this 20-char key redacts to `AK...YA`. Match the
    // exact redaction so the finding is unambiguously the planted SECRET.
    let edge = (SECRET.len() / 8).clamp(1, 4);
    let expected_redacted = format!("{}...{}", &SECRET[..edge], &SECRET[SECRET.len() - edge..]);
    let aws = findings
        .iter()
        .find(|finding| {
            finding["detector_id"] == "aws-access-key"
                && finding["credential_redacted"].as_str() == Some(expected_redacted.as_str())
        })
        .unwrap_or_else(|| panic!("missing decoded AWS key finding; findings={findings:#?}"));

    assert_eq!(aws["location"]["line"], 1);
    let offset = aws["location"]["offset"]
        .as_u64()
        .expect("location.offset must be numeric") as usize;
    assert!(
        offset < text.len(),
        "decoded finding offset {offset} must be inside the {}-byte source file",
        text.len()
    );
    assert!(
        (encoded_start..encoded_end).contains(&offset),
        "decoded finding offset {offset} must point inside the encoded base64 run \
         {encoded_start}..{encoded_end} in the source file"
    );
}

#[test]
fn scan_k8s_secret_base64_decodes_secret_at_encoded_source_offset() {
    const ENCODED_ASSIGNMENT: &str = "QVdTX0FDQ0VTU19LRVlfSUQ9QUtJQVFZTFBNTjVIRklRUjdYWUE=";
    let text = format!(
        "apiVersion: v1\n\
         kind: Secret\n\
         metadata:\n\
         \x20\x20name: aws-creds\n\
         data:\n\
         \x20\x20aws_credentials: {ENCODED_ASSIGNMENT}\n"
    );
    let encoded_start = text
        .find(ENCODED_ASSIGNMENT)
        .expect("fixture contains encoded assignment");
    let encoded_end = encoded_start + ENCODED_ASSIGNMENT.len();
    let (_dir, path) = write_temp_file("k8s-secret.yaml", &text);

    let output = scan_path(&path, &[]);
    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert_eq!(
        output.status.code(),
        Some(1),
        "k8s Secret base64 data must surface the decoded AWS key; stderr={stderr}; stdout={stdout}"
    );

    let findings = serde_json::from_str::<serde_json::Value>(&stdout)
        .expect("json")
        .as_array()
        .expect("array")
        .clone();
    let aws = findings
        .iter()
        .find(|finding| finding["detector_id"] == "aws-access-key")
        .unwrap_or_else(|| panic!("missing decoded AWS key finding; findings={findings:#?}"));

    assert_eq!(aws["location"]["line"], 6);
    let offset = aws["location"]["offset"]
        .as_u64()
        .expect("location.offset must be numeric") as usize;
    assert!(
        offset < text.len(),
        "decoded k8s finding offset {offset} must be inside the {}-byte source file",
        text.len()
    );
    assert!(
        (encoded_start..encoded_end).contains(&offset),
        "decoded k8s finding offset {offset} must point inside the encoded base64 run \
         {encoded_start}..{encoded_end} in the source file"
    );
}