guests/static-scan/tests/registry.rs
// SPDX-License-Identifier: AGPL-3.0-only
//! The registry check (yanked, deprecated, retracted; modgunn::registry) and
//! the malicious check (OSV MAL- records; modgunn::feeds::parse_malicious), on
//! the registry documents the caller sent. Heimdall is a defensive sandbox.
mod common;
use common::*;
use heimdall_scan::report::unknown;
use heimdall_scan::{Check, CheckResult, DataKind, Severity, Verdict};
const CRATES: &[u8] = br#"{"crate":{"name":"smallvec"},"versions":[
{"num":"1.6.0","yanked":true,"crate":"smallvec"},
{"num":"1.6.1","yanked":false,"crate":"smallvec"},
{"num":"1.13.2","yanked":false,"crate":"smallvec"}]}"#;
const NPM: &[u8] = br#"{"name":"request","versions":{
"2.88.2":{"deprecated":"request has been deprecated, see https://github.com/request/request/issues/3142"},
"2.88.0":{}}}"#;
const PYPI: &[u8] = br#"{"info":{"name":"PyYAML"},"releases":{
"5.3":[{"yanked":true,"yanked_reason":"security"}],
"5.3.1":[{"yanked":false}]}}"#;
const GOMOD: &[u8] = b"module github.com/acme/widget\n\ngo 1.22\n\nretract (\n v1.2.0 // published by accident\n [v1.3.0, v1.3.5]\n)\n";
const MAL: &[u8] = br#"[{"id":"MAL-2025-1234","summary":"Malicious code in evil-pkg (npm)",
"affected":[{"package":{"ecosystem":"npm","name":"evil-pkg"}}]},
{"id":"MAL-2025-9999","summary":"Malicious code in nasty (PyPI)",
"affected":[{"package":{"ecosystem":"PyPI","name":"nasty"},"versions":["0.1.0"]}]}]"#;
fn reg(purl: &str, data: Option<&[u8]>) -> CheckResult {
let d: Vec<(DataKind, &[u8])> = data
.map(|d| vec![(DataKind::Registry, d)])
.unwrap_or_default();
check(
&envelope(b"", &d),
&[("purl", purl), ("checks", "registry")],
Check::Registry,
)
}
fn mal(purl: &str, data: Option<&[u8]>) -> CheckResult {
let d: Vec<(DataKind, &[u8])> = data
.map(|d| vec![(DataKind::Malicious, d)])
.unwrap_or_default();
check(
&envelope(b"", &d),
&[("purl", purl), ("checks", "malicious")],
Check::Malicious,
)
}
#[test]
fn a_yanked_crate_version_is_a_registry_finding_with_the_upgrade_target() {
let r = reg("pkg:cargo/smallvec@1.6.0", Some(CRATES));
assert_eq!(r.verdict, Verdict::Findings);
assert_eq!(r.findings[0].id, "MODGUNN-YANK-cargo-smallvec-1.6.0");
assert!(
r.findings[0].summary.contains("1.6.1"),
"{:?}",
r.findings[0]
);
}
#[test]
fn a_version_that_is_not_yanked_is_clean() {
assert_clean(®("pkg:cargo/smallvec@1.13.2", Some(CRATES)));
}
#[test]
fn a_deprecated_npm_version_is_a_finding() {
let r = reg("pkg:npm/request@2.88.2", Some(NPM));
assert!(has(&r, "MODGUNN-DEPRECATED-npm-request-2.88.2"), "{r:?}");
assert_clean(®("pkg:npm/request@2.88.0", Some(NPM)));
}
#[test]
fn a_yanked_pypi_release_is_a_finding_and_names_compare_by_pep_503() {
let r = reg("pkg:pypi/pyyaml@5.3", Some(PYPI));
assert!(has(&r, "MODGUNN-YANK-pypi-PyYAML-5.3"), "{r:?}");
assert_clean(®("pkg:pypi/PyYAML@5.3.1", Some(PYPI)));
}
#[test]
fn a_retracted_go_version_is_a_finding_including_a_retracted_range() {
assert!(
reg("pkg:golang/github.com/acme/widget@v1.2.0", Some(GOMOD)).verdict == Verdict::Findings
);
assert!(
reg("pkg:golang/github.com/acme/widget@v1.3.2", Some(GOMOD)).verdict == Verdict::Findings
);
assert_clean(®(
"pkg:golang/github.com/acme/widget@v1.4.0",
Some(GOMOD),
));
}
#[test]
fn a_registry_document_about_another_package_is_unknown_never_clean() {
assert_unknown(
®("pkg:cargo/serde@1.6.1", Some(CRATES)),
unknown::DATA_INVALID,
);
assert_unknown(
®("pkg:npm/lodash@2.88.0", Some(NPM)),
unknown::DATA_INVALID,
);
assert_unknown(
®("pkg:golang/github.com/evil/widget@v1.0.0", Some(GOMOD)),
unknown::DATA_INVALID,
);
}
#[test]
fn a_version_missing_from_the_registry_document_is_unknown_as_a_stale_snapshot() {
assert_unknown(
®("pkg:cargo/smallvec@9.9.9", Some(CRATES)),
unknown::DATA_INVALID,
);
}
#[test]
fn a_missing_registry_snapshot_is_unknown() {
assert_unknown(
®("pkg:cargo/smallvec@1.6.0", None),
unknown::DATA_MISSING,
);
}
#[test]
fn a_malformed_registry_document_is_unknown() {
for bad in [
&b"{"[..],
b"[]",
br#"{"crate":{"name":"smallvec"}}"#,
b"\xff\xfe",
] {
assert_unknown(
®("pkg:cargo/smallvec@1.6.0", Some(bad)),
unknown::DATA_INVALID,
);
}
}
#[test]
fn a_purl_without_a_version_is_unknown_for_the_registry() {
assert_unknown(
®("pkg:cargo/smallvec", Some(CRATES)),
unknown::NO_VERSION,
);
}
#[test]
fn an_ecosystem_without_a_registry_reader_is_unknown() {
assert_unknown(
®(
"pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1",
Some(b"{}"),
),
unknown::UNSUPPORTED_ECOSYSTEM,
);
}
#[test]
fn a_package_on_the_malicious_feed_is_a_critical_finding() {
let r = mal("pkg:npm/evil-pkg@1.0.0", Some(MAL));
assert_eq!(r.verdict, Verdict::Findings);
assert_eq!(r.findings[0].id, "MAL-2025-1234");
assert_eq!(r.findings[0].severity, Severity::Critical);
}
#[test]
fn a_versioned_malicious_record_matches_only_its_versions() {
assert!(has(
&mal("pkg:pypi/nasty@0.1.0", Some(MAL)),
"MAL-2025-9999"
));
assert_clean(&mal("pkg:pypi/nasty@0.2.0", Some(MAL)));
}
#[test]
fn a_package_not_on_the_feed_is_clean() {
assert_clean(&mal("pkg:npm/lodash@4.17.21", Some(MAL)));
}
#[test]
fn an_empty_feed_array_is_a_real_none() {
assert_clean(&mal("pkg:npm/lodash@4.17.21", Some(b"[]")));
}
#[test]
fn a_single_osv_record_feed_reads_too() {
let one = br#"{"id":"MAL-2025-1","summary":"x","affected":[{"package":{"ecosystem":"crates.io","name":"evil"}}]}"#;
assert!(has(&mal("pkg:cargo/evil@0.1.0", Some(one)), "MAL-2025-1"));
}
#[test]
fn a_missing_or_garbage_feed_is_unknown() {
assert_unknown(&mal("pkg:npm/evil-pkg@1.0.0", None), unknown::DATA_MISSING);
assert_unknown(
&mal("pkg:npm/evil-pkg@1.0.0", Some(b"not json")),
unknown::DATA_INVALID,
);
}
#[test]
fn without_a_version_whole_package_records_still_match_and_the_rest_is_unknown() {
let r = mal("pkg:npm/evil-pkg", Some(MAL));
assert_eq!(r.verdict, Verdict::Findings);
assert_unknown(&mal("pkg:pypi/nasty", Some(MAL)), unknown::NO_VERSION);
}