use std::path::PathBuf;
use std::process::Command;
use tempfile::TempDir;
fn binary() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_keyhog"))
}
fn live_verify_enabled() -> bool {
std::env::var("KEYHOG_LIVE_VERIFY")
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
.unwrap_or(false)
}
const EXIT_LIVE_CREDENTIALS: i32 = 10;
#[derive(Debug, Clone)]
struct LiveCredential {
label: &'static str,
file_name: &'static str,
contents: String,
}
fn collect_live_credentials() -> Vec<LiveCredential> {
let mut out = Vec::new();
let aws_id = std::env::var("KEYHOG_LIVE_AWS_ACCESS_KEY_ID").ok();
let aws_secret = std::env::var("KEYHOG_LIVE_AWS_SECRET_ACCESS_KEY").ok();
if let (Some(id), Some(secret)) = (aws_id, aws_secret) {
if !id.trim().is_empty() && !secret.trim().is_empty() {
out.push(LiveCredential {
label: "aws-credentials",
file_name: "live.env",
contents: format!(
"AWS_ACCESS_KEY_ID={}\nAWS_SECRET_ACCESS_KEY={}\n",
id.trim(),
secret.trim()
),
});
}
}
if let Ok(pat) = std::env::var("KEYHOG_LIVE_GITHUB_PAT") {
if !pat.trim().is_empty() {
out.push(LiveCredential {
label: "github-personal-access-token",
file_name: "live-gh.env",
contents: format!("GITHUB_TOKEN={}\n", pat.trim()),
});
}
}
out
}
#[test]
fn live_verify_smoke_real_credentials_yield_exit_10() {
if !live_verify_enabled() {
eprintln!(
"live_verify_smoke: skipped - set KEYHOG_LIVE_VERIFY=1 and \
at least one of KEYHOG_LIVE_AWS_ACCESS_KEY_ID/SECRET, \
KEYHOG_LIVE_GITHUB_PAT to run the live-verify smoke."
);
return;
}
let creds = collect_live_credentials();
if creds.is_empty() {
panic!(
"KEYHOG_LIVE_VERIFY=1 but no credentials supplied. Set at \
least one of: (KEYHOG_LIVE_AWS_ACCESS_KEY_ID + \
KEYHOG_LIVE_AWS_SECRET_ACCESS_KEY) or KEYHOG_LIVE_GITHUB_PAT."
);
}
for cred in &creds {
let dir = TempDir::new().expect("tempdir");
let path = dir.path().join(cred.file_name);
std::fs::write(&path, &cred.contents).expect("write planted credential");
let out = Command::new(binary())
.arg("scan")
.arg("--no-daemon")
.arg("--verify")
.arg("--format")
.arg("json")
.arg(&path)
.output()
.expect("spawn keyhog scan --verify");
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
let code = out.status.code();
assert_eq!(
code,
Some(EXIT_LIVE_CREDENTIALS),
"{label}: expected exit {expected} (EXIT_LIVE_CREDENTIALS) \
but got {code:?}. The verifier's network roundtrip for \
this credential class is broken - investigate {verifier_module}.\n\
stdout:\n{stdout}\nstderr:\n{stderr}",
label = cred.label,
expected = EXIT_LIVE_CREDENTIALS,
verifier_module = match cred.label {
"aws-credentials" => "crates/verifier/src/verify/aws.rs",
"github-personal-access-token" => "crates/verifier/src/verify/mod.rs",
_ => "crates/verifier/src/verify/",
},
);
assert!(
stdout.contains("\"Live\"") || stdout.contains("\"live\""),
"{label}: exit was 10 but JSON did not surface a Live \
classification. orchestrator.rs and the report writer \
are out of sync.\nstdout:\n{stdout}",
label = cred.label,
);
}
}