guests/static-scan/tests/wasm_e2e.rs
// SPDX-License-Identifier: AGPL-3.0-only
//! The guest as Heimdall runs it: built for wasm32, componentized, signed,
//! loaded by the real host (no WASI) and run under the default limits. Its
//! findings documents are byte for byte the native ones. Heimdall is a
//! defensive sandbox.
mod common;
use common::guests::{core_module, loaded};
use common::*;
use heimdall_guest_static_scan::Outcome;
use heimdall_scan::{codes, DataKind, Report, Verdict, REPORT_NAME};
use heimdall_wasm::{codes as host_codes, GuestOutcome, RunLimits};
fn params(p: &[(&str, &str)]) -> heimdall_core::Params {
p.iter()
.map(|(k, v)| ((*k).to_owned(), (*v).to_owned()))
.collect()
}
fn in_wasm(input: &[u8], p: &[(&str, &str)]) -> GuestOutcome {
loaded().guest.run(input, ¶ms(p), &RunLimits::default())
}
/// Run in the sandbox and natively; the sandbox must give the same document.
fn same(input: &[u8], p: &[(&str, &str)]) -> Report {
let o = in_wasm(input, p);
let heimdall_wasm::Verdict::Accepted { note } = &o.verdict else {
panic!("refused in wasm: {}", o.verdict)
};
assert_eq!(o.files.len(), 1, "exactly one file");
assert_eq!(o.files[0].name, REPORT_NAME);
let Outcome::Report { report, json } = run(input, p) else {
panic!("refused natively")
};
assert_eq!(
o.files[0].bytes, json,
"the sandbox and the library disagree"
);
assert_eq!(note, &Outcome::note(&report));
Report::from_canonical_json(&o.files[0].bytes).unwrap()
}
#[test]
fn the_guest_module_imports_nothing_but_the_wit() {
assert_eq!(
common::guests::imports(core_module()),
vec!["$root::emit-file".to_string()]
);
}
#[test]
fn the_host_loads_the_signed_guest_component_with_no_wasi() {
let _ = loaded();
}
#[test]
fn an_npm_dropper_is_a_finding_inside_the_sandbox_with_the_native_bytes() {
let r = same(
&envelope(&npm_tgz(NPM_DROPPER), &[]),
&[("purl", "pkg:npm/evil-pkg@1.0.0")],
);
assert_eq!(r.verdict, Verdict::Findings);
}
#[test]
fn a_pypi_dropper_typosquat_and_yank_all_judge_inside_the_sandbox() {
let art = sdist(&[(b"reqests-1.0/setup.py", SETUP_DROPPER)]);
let input = envelope(
&art,
&[
(DataKind::PopularNames, b"requests\n"),
(DataKind::Registry, br#"{"info":{"name":"reqests"},"releases":{"1.0":[{"yanked":true}]}}"#),
(DataKind::Malicious, br#"[{"id":"MAL-2025-7","affected":[{"package":{"ecosystem":"PyPI","name":"reqests"}}]}]"#),
(DataKind::InternalNamespaces, b""),
],
);
let r = same(
&input,
&[("purl", "pkg:pypi/reqests@1.0"), ("origin", "public")],
);
for c in &r.checks {
let want = if c.check == heimdall_scan::Check::DependencyConfusion {
Verdict::Clean
} else {
Verdict::Findings
};
assert_eq!(c.verdict, want, "{c:?}");
}
}
#[test]
fn a_benign_wheel_is_clean_inside_the_sandbox() {
let r = same(
&envelope(&wheel(&[]), &[]),
&[("purl", "pkg:pypi/okpkg@1.0"), ("checks", "behavior")],
);
assert_eq!(r.verdict, Verdict::Clean);
}
#[test]
fn a_missing_snapshot_is_unknown_inside_the_sandbox() {
let r = same(
&envelope(b"", &[]),
&[("purl", "pkg:npm/lodashs@1.0.0"), ("checks", "typosquat")],
);
assert_eq!(r.verdict, Verdict::Unknown);
}
#[test]
fn a_gzip_bomb_is_unknown_inside_the_sandbox_under_the_default_limits() {
let zeros = vec![0u8; 80 << 20];
let art = tgz(&[
T::File(b"package/package.json", NPM_BENIGN),
T::File(b"package/z", &zeros),
]);
let o = in_wasm(
&envelope(&art, &[]),
&[("purl", "pkg:npm/x@1.0.0"), ("checks", "behavior")],
);
// Either the guest's own cap answers unknown, or the host's fuel stops it:
// never clean.
match &o.verdict {
heimdall_wasm::Verdict::Accepted { .. } => {
let r = Report::from_canonical_json(&o.files[0].bytes).unwrap();
assert_eq!(r.verdict, Verdict::Unknown, "{r:?}");
}
heimdall_wasm::Verdict::Refused { code, .. } => assert_eq!(*code, host_codes::OUT_OF_FUEL),
}
}
#[test]
fn path_traversal_and_zip_overlap_are_findings_inside_the_sandbox() {
let art = tgz(&[
T::File(b"package/package.json", NPM_BENIGN),
T::File(b"../../x", b"x"),
]);
let r = same(
&envelope(&art, &[]),
&[("purl", "pkg:npm/x@1.0.0"), ("checks", "behavior")],
);
assert_eq!(r.verdict, Verdict::Findings);
}
#[test]
fn a_guest_refusal_is_heimdall_wasm_1_with_the_bracketed_scan_code() {
let o = in_wasm(b"garbage", &[("purl", "pkg:npm/x@1.0.0")]);
match o.verdict {
heimdall_wasm::Verdict::Refused { code, reason } => {
assert_eq!(code, host_codes::GUEST_REFUSED);
assert!(
reason.starts_with(&format!("[{}]", codes::BAD_INPUT)),
"{reason}"
);
}
v => panic!("accepted: {v}"),
}
assert!(o.files.is_empty());
let o = in_wasm(&envelope(b"", &[]), &[]);
match o.verdict {
heimdall_wasm::Verdict::Refused { reason, .. } => {
assert!(
reason.starts_with(&format!("[{}]", codes::BAD_PURL)),
"{reason}"
)
}
v => panic!("accepted: {v}"),
}
}
#[test]
fn hostile_inputs_never_trap_the_guest() {
let inputs: Vec<Vec<u8>> = vec![
envelope(&[0x1f, 0x8b, 8, 0, 0, 0, 0, 0, 0, 3], &[]),
envelope(b"PK\x05\x06", &[]),
envelope(b"PK\x03\x04\xff\xff\xff\xff", &[]),
envelope(&gz(&[0xffu8; 1500]), &[]),
envelope(&npm_tgz(NPM_BENIGN)[..40], &[]),
envelope(
b"",
&[
(DataKind::Registry, b"\xff"),
(DataKind::Malicious, b"[[[[[[[[[[[[[[[[[[[[[["),
],
),
];
for i in &inputs {
let o = in_wasm(i, &[("purl", "pkg:npm/x@1.0.0")]);
assert!(o.verdict.is_accepted(), "{}", o.verdict);
}
}
#[test]
fn deeply_nested_json_in_a_snapshot_never_traps_the_guest() {
let deep = format!("{}{}", "[".repeat(100_000), "]".repeat(100_000));
let o = in_wasm(
&envelope(
b"",
&[
(DataKind::Malicious, deep.as_bytes()),
(DataKind::Registry, deep.as_bytes()),
],
),
&[
("purl", "pkg:npm/x@1.0.0"),
("checks", "registry,malicious"),
],
);
assert!(o.verdict.is_accepted(), "{}", o.verdict);
let r = Report::from_canonical_json(&o.files[0].bytes).unwrap();
assert_eq!(r.verdict, Verdict::Unknown);
}