use keyhog_core::{finding_metadata, key_id_canary_status, parse_canary_account_ids};
#[test]
fn decodes_canonical_truffle_sample() {
assert_eq!(
keyhog_core::testing::CoreTestApi::aws_account_from_key_id(
&keyhog_core::testing::TestApi,
"ASIAY34FZKBOKMUTVV7A"
)
.as_deref(),
Some("609629065308")
);
}
#[test]
fn decodes_akia_with_leading_zero_account() {
assert_eq!(
keyhog_core::testing::CoreTestApi::aws_account_from_key_id(
&keyhog_core::testing::TestApi,
"AKIAAYLPMN5HAAAAAAAA"
)
.as_deref(),
Some("052310077262")
);
}
#[test]
fn rejects_non_aws_and_malformed_ids() {
assert_eq!(
keyhog_core::testing::CoreTestApi::aws_account_from_key_id(
&keyhog_core::testing::TestApi,
"not-an-aws-key"
),
None
);
assert_eq!(
keyhog_core::testing::CoreTestApi::aws_account_from_key_id(
&keyhog_core::testing::TestApi,
"AKIA1234"
),
None
);
assert_eq!(
keyhog_core::testing::CoreTestApi::aws_account_from_key_id(
&keyhog_core::testing::TestApi,
"ZZZZY34FZKBOKMUTVV7A"
),
None
);
assert_eq!(
keyhog_core::testing::CoreTestApi::aws_account_from_key_id(
&keyhog_core::testing::TestApi,
"ASIAy34FZKBOKMUTVV7A"
),
None
);
}
#[test]
fn canary_account_is_recognised() {
assert!(keyhog_core::testing::CoreTestApi::aws_account_is_canary(
&keyhog_core::testing::TestApi,
"052310077262"
));
assert!(keyhog_core::testing::CoreTestApi::aws_account_is_canary(
&keyhog_core::testing::TestApi,
"044858866125"
));
assert!(!keyhog_core::testing::CoreTestApi::aws_account_is_canary(
&keyhog_core::testing::TestApi,
"609629065308"
));
}
#[test]
fn canary_tier_b_parser_rejects_invalid_account_files() {
let malformed = keyhog_core::testing::CoreTestApi::parse_aws_canary_accounts_for_test(
&keyhog_core::testing::TestApi,
"not = [",
)
.expect_err("malformed AWS canary TOML must fail closed");
assert!(
malformed.contains("invalid aws-canary-accounts.toml"),
"unexpected malformed error: {malformed}"
);
let blank = keyhog_core::testing::CoreTestApi::parse_aws_canary_accounts_for_test(
&keyhog_core::testing::TestApi,
"[canary]\naccounts = [\" \"]\n",
)
.expect_err("blank AWS canary account must fail closed");
assert!(
blank.contains("must not be empty"),
"unexpected blank-account error: {blank}"
);
let malformed_account = keyhog_core::testing::CoreTestApi::parse_aws_canary_accounts_for_test(
&keyhog_core::testing::TestApi,
"[canary]\naccounts = [\"1234\"]\n",
)
.expect_err("short AWS canary account must fail closed");
assert!(
malformed_account.contains("12-digit AWS account id"),
"unexpected malformed-account error: {malformed_account}"
);
}
#[test]
fn configured_canary_account_parser_accepts_unique_toml_values() {
let parsed = parse_canary_account_ids([" 609629065308 ", "609629065308", "000000000001"])
.expect("valid configured AWS account IDs");
assert_eq!(parsed.len(), 2);
assert!(parsed.contains("609629065308"));
assert!(parsed.contains("000000000001"));
let invalid = parse_canary_account_ids(["1234"])
.expect_err("configured AWS account IDs must be 12 digits");
assert!(
invalid.contains("12-digit AWS account id"),
"unexpected invalid account error: {invalid}"
);
}
#[test]
fn canary_parser_rejects_confusable_and_overlong_account_ids() {
let confusable = keyhog_core::testing::CoreTestApi::parse_aws_canary_accounts_for_test(
&keyhog_core::testing::TestApi,
"[canary]\naccounts = [\"12345678901O\"]\n", )
.expect_err("a 12-char id with a non-digit must fail closed");
assert!(
confusable.contains("12-digit AWS account id"),
"unexpected confusable-account error: {confusable}"
);
let overlong = keyhog_core::testing::CoreTestApi::parse_aws_canary_accounts_for_test(
&keyhog_core::testing::TestApi,
"[canary]\naccounts = [\"1234567890123\"]\n", )
.expect_err("a 13-digit id must fail closed");
assert!(
overlong.contains("12-digit AWS account id"),
"unexpected overlong-account error: {overlong}"
);
}
#[test]
fn canary_parser_merges_knockoff_table_into_the_canary_set() {
let merged = keyhog_core::testing::CoreTestApi::parse_aws_canary_accounts_for_test(
&keyhog_core::testing::TestApi,
"[canary]\naccounts = [\"000000000001\"]\n[knockoff]\naccounts = [\" 000000000002 \"]\n",
)
.expect("valid canary + knockoff accounts must parse");
assert_eq!(
merged.len(),
2,
"both the canary and the knockoff account must be present: {merged:?}"
);
assert!(
merged.contains("000000000001"),
"first-party canary account must be recognized: {merged:?}"
);
assert!(
merged.contains("000000000002"),
"knockoff account must be merged (and trimmed) into the same set: {merged:?}"
);
}
#[test]
fn canary_key_id_round_trips_through_decode() {
assert!(key_id_canary_status("AKIAAYLPMN5HAAAAAAAA").expect("canary status"));
assert!(!key_id_canary_status("ASIAY34FZKBOKMUTVV7A").expect("canary status"));
assert!(!key_id_canary_status("hunter2").expect("canary status"));
}
#[test]
fn finding_metadata_surfaces_account_id() {
let meta = finding_metadata("ASIAY34FZKBOKMUTVV7A").expect("decodable");
assert_eq!(
meta.get("account_id").map(String::as_str),
Some("609629065308")
);
assert!(finding_metadata("hunter2").is_none());
}
#[test]
fn finding_metadata_flags_canary_and_suppression_note() {
let meta = finding_metadata("AKIAAYLPMN5HAAAAAAAA").expect("decodable canary key");
assert_eq!(
meta.get("account_id").map(String::as_str),
Some("052310077262")
);
assert_eq!(meta.get("is_canary").map(String::as_str), Some("true"));
assert!(meta
.get("canary_message")
.is_some_and(|m| m.contains("canarytokens.org")));
let normal = finding_metadata("ASIAY34FZKBOKMUTVV7A").expect("decodable");
assert_eq!(normal.get("is_canary"), None);
}
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(10_000))]
#[test]
fn parse_canary_account_ids_accepts_iff_trimmed_is_12_ascii_digits(
raw in "[0-9a ]{0,16}"
) {
let trimmed = raw.trim();
let oracle_valid =
trimmed.len() == 12 && trimmed.bytes().all(|byte| byte.is_ascii_digit());
let result = parse_canary_account_ids([raw.as_str()]);
prop_assert_eq!(
result.is_ok(),
oracle_valid,
"accept/reject must match the exactly-12-ASCII-digits oracle: raw={:?} trimmed={:?}",
raw,
trimmed
);
if let Ok(set) = result {
prop_assert!(
set.contains(trimmed),
"an accepted account must be stored in its TRIMMED form: raw={:?}",
raw
);
}
}
}