From a079b6c4182daa3bb22f92928a44126412950751 Mon Sep 17 00:00:00 2001 From: mohammad junayd <111487869+m-jay21@users.noreply.github.com> Date: Mon, 14 Sep 2026 17:47:03 +0400 Subject: [PATCH] test(install): cover checksum and provenance verification Pin fail-closed installer paths for missing manifests, verifier fallback, and the skip-verify escape hatch (#1267). --- tests/test_install_script.py | 207 ++++++++++++++++++++++++++++++++++- 1 file changed, 204 insertions(+), 3 deletions(-) diff --git a/tests/test_install_script.py b/tests/test_install_script.py index eec83ba9..c2fd5015 100644 --- a/tests/test_install_script.py +++ b/tests/test_install_script.py @@ -48,14 +48,42 @@ esac """, ) _write_executable(mock_bin / "docker", "#!/bin/sh\nexit 0\n") + # Shadow any real gh/cosign on PATH. Behaviour is driven by env flags. _write_executable( mock_bin / "gh", """#!/bin/sh -# Prefer this mock over a real gh on PATH so tests do not hit GitHub. -if [ "$1" = "attestation" ]; then +if [ -n "${STRIX_TEST_NO_VERIFIER:-}" ]; then + exit 1 +fi +if [ "$1" = "attestation" ] && [ "$2" = "verify" ]; then + if [ "$3" = "--help" ]; then + exit 0 + fi + printf '%s\\n' "$*" >> "$STRIX_TEST_GH_LOG" + if [ -n "${STRIX_TEST_GH_FAIL_VERIFY:-}" ]; then + exit 1 + fi exit 0 fi exit 1 +""", + ) + _write_executable( + mock_bin / "cosign", + """#!/bin/sh +if [ -n "${STRIX_TEST_NO_VERIFIER:-}" ]; then + exit 1 +fi +if [ "$1" = "verify-blob-attestation" ]; then + if [ "$2" = "--help" ]; then + [ -n "${STRIX_TEST_USE_COSIGN:-}" ] && exit 0 + exit 1 + fi + printf '%s\\n' "$*" >> "$STRIX_TEST_COSIGN_LOG" + [ -n "${STRIX_TEST_USE_COSIGN:-}" ] && exit 0 + exit 1 +fi +exit 1 """, ) _write_executable( @@ -75,11 +103,19 @@ while [ "$#" -gt 0 ]; do printf '%s\\n' "$1" >> "$STRIX_TEST_CURL_LOG" shift done +if [ -n "${STRIX_TEST_CURL_FAIL:-}" ]; then + case "$url" in + *"$STRIX_TEST_CURL_FAIL"*) exit 22 ;; + esac +fi case "$url" in */SHA256SUMS) name=$(basename "$STRIX_TEST_ARCHIVE") if [ -n "${STRIX_TEST_BAD_CHECKSUM:-}" ]; then printf '%s %s\\n' "0" "$name" > "$output" + elif [ -n "${STRIX_TEST_SUMS_WRONG_NAME:-}" ]; then + hash=$(sha256sum "$STRIX_TEST_ARCHIVE" | awk '{print $1}') + printf '%s %s\\n' "$hash" "other-file.tar.gz" > "$output" else hash=$(sha256sum "$STRIX_TEST_ARCHIVE" | awk '{print $1}') printf '%s %s\\n' "$hash" "$name" > "$output" @@ -113,6 +149,8 @@ def _create_installer_environment( download_path = tmp_path / "downloads" download_path.mkdir() curl_log_path = tmp_path / "curl.log" + gh_log_path = tmp_path / "gh.log" + cosign_log_path = tmp_path / "cosign.log" environment = { "HOME": str(home_path), "XDG_CONFIG_HOME": str(home_path / ".config"), @@ -121,6 +159,8 @@ def _create_installer_environment( "TMPDIR": str(download_path), "STRIX_TEST_ARCHIVE": str(archive_path), "STRIX_TEST_CURL_LOG": str(curl_log_path), + "STRIX_TEST_GH_LOG": str(gh_log_path), + "STRIX_TEST_COSIGN_LOG": str(cosign_log_path), "VERSION": RELEASE_VERSION, } return environment, home_path, curl_log_path @@ -154,7 +194,20 @@ def test_installer_downloads_and_runs_linux_arm64_release(tmp_path: Path) -> Non assert result.returncode == 0, result.stderr expected_filename = f"strix-{RELEASE_VERSION}-{RELEASE_TARGET}.tar.gz" - assert expected_filename in curl_log_path.read_text(encoding="utf-8") + curl_log = curl_log_path.read_text(encoding="utf-8") + assert expected_filename in curl_log + assert f"releases/download/v{RELEASE_VERSION}/SHA256SUMS" in curl_log + assert f"strix-{RELEASE_TARGET}.intoto.jsonl" in curl_log + assert "Checksum verified" in result.stdout + assert "Provenance verified" in result.stdout + gh_log = (tmp_path / "gh.log").read_text(encoding="utf-8") + assert "--repo usestrix/strix" in gh_log + assert "--bundle strix-linux-arm64.intoto.jsonl" in gh_log + assert ( + "--signer-workflow usestrix/strix/.github/workflows/build-release.yml" in gh_log + ) + assert "--deny-self-hosted-runners" in gh_log + assert not (tmp_path / "cosign.log").exists() installed_binary = home_path / ".strix/bin/strix" installed_result = subprocess.run( # noqa: S603 @@ -215,3 +268,151 @@ def test_installer_leaves_existing_install_on_checksum_failure(tmp_path: Path) - check=True, ) assert installed_result.stdout.strip() == "strix 1.0.0" + + +def test_installer_rejects_missing_checksum_manifest(tmp_path: Path) -> None: + repository_root = Path(__file__).resolve().parents[1] + archive_path = _create_release_archive(tmp_path) + mock_bin = _create_mock_commands(tmp_path, machine="aarch64") + environment, home_path, _curl_log_path = _create_installer_environment( + tmp_path, + archive_path, + mock_bin, + ) + environment["STRIX_TEST_CURL_FAIL"] = "SHA256SUMS" + + result = _run_installer(repository_root, environment) + + assert result.returncode != 0 + assert "Failed to download checksum manifest" in result.stdout + assert not (home_path / ".strix/bin/strix").exists() + + +def test_installer_rejects_checksum_manifest_without_archive_entry( + tmp_path: Path, +) -> None: + repository_root = Path(__file__).resolve().parents[1] + archive_path = _create_release_archive(tmp_path) + mock_bin = _create_mock_commands(tmp_path, machine="aarch64") + environment, home_path, _curl_log_path = _create_installer_environment( + tmp_path, + archive_path, + mock_bin, + ) + environment["STRIX_TEST_SUMS_WRONG_NAME"] = "1" + + result = _run_installer(repository_root, environment) + + assert result.returncode != 0 + assert "No SHA256SUMS entry" in result.stdout + assert not (home_path / ".strix/bin/strix").exists() + + +def test_installer_rejects_missing_provenance_bundle(tmp_path: Path) -> None: + repository_root = Path(__file__).resolve().parents[1] + archive_path = _create_release_archive(tmp_path) + mock_bin = _create_mock_commands(tmp_path, machine="aarch64") + environment, home_path, _curl_log_path = _create_installer_environment( + tmp_path, + archive_path, + mock_bin, + ) + environment["STRIX_TEST_CURL_FAIL"] = "intoto.jsonl" + + result = _run_installer(repository_root, environment) + + assert result.returncode != 0 + assert "Failed to download provenance bundle" in result.stdout + assert "Checksum verified" in result.stdout + assert not (home_path / ".strix/bin/strix").exists() + + +def test_installer_rejects_failed_gh_attestation_verify(tmp_path: Path) -> None: + repository_root = Path(__file__).resolve().parents[1] + archive_path = _create_release_archive(tmp_path) + mock_bin = _create_mock_commands(tmp_path, machine="aarch64") + environment, home_path, _curl_log_path = _create_installer_environment( + tmp_path, + archive_path, + mock_bin, + ) + environment["STRIX_TEST_GH_FAIL_VERIFY"] = "1" + + result = _run_installer(repository_root, environment) + + assert result.returncode != 0 + assert "gh attestation verify failed" in result.stdout + assert not (home_path / ".strix/bin/strix").exists() + assert not (tmp_path / "cosign.log").exists() + + +def test_installer_uses_cosign_when_gh_cannot_verify(tmp_path: Path) -> None: + repository_root = Path(__file__).resolve().parents[1] + archive_path = _create_release_archive(tmp_path) + mock_bin = _create_mock_commands(tmp_path, machine="aarch64") + environment, home_path, _curl_log_path = _create_installer_environment( + tmp_path, + archive_path, + mock_bin, + ) + environment["STRIX_TEST_USE_COSIGN"] = "1" + _write_executable( + mock_bin / "gh", + "#!/bin/sh\nexit 1\n", + ) + + result = _run_installer(repository_root, environment) + + assert result.returncode == 0, result.stderr + assert "Provenance verified" in result.stdout + assert "(cosign)" in result.stdout + cosign_log = (tmp_path / "cosign.log").read_text(encoding="utf-8") + assert "--new-bundle-format" in cosign_log + assert "--certificate-oidc-issuer https://token.actions.githubusercontent.com" in ( + cosign_log + ) + assert "build-release.yml" in cosign_log + assert "--type slsaprovenance1" in cosign_log + assert (home_path / ".strix/bin/strix").exists() + + +def test_installer_rejects_when_no_provenance_verifier_is_available( + tmp_path: Path, +) -> None: + repository_root = Path(__file__).resolve().parents[1] + archive_path = _create_release_archive(tmp_path) + mock_bin = _create_mock_commands(tmp_path, machine="aarch64") + environment, home_path, _curl_log_path = _create_installer_environment( + tmp_path, + archive_path, + mock_bin, + ) + environment["STRIX_TEST_NO_VERIFIER"] = "1" + + result = _run_installer(repository_root, environment) + + assert result.returncode != 0 + assert "Neither a usable 'gh' nor 'cosign'" in result.stdout + assert not (home_path / ".strix/bin/strix").exists() + + +def test_installer_skip_verify_does_not_download_manifests(tmp_path: Path) -> None: + repository_root = Path(__file__).resolve().parents[1] + archive_path = _create_release_archive(tmp_path) + mock_bin = _create_mock_commands(tmp_path, machine="aarch64") + environment, home_path, curl_log_path = _create_installer_environment( + tmp_path, + archive_path, + mock_bin, + ) + environment["STRIX_INSTALL_SKIP_VERIFY"] = "1" + + result = _run_installer(repository_root, environment) + + assert result.returncode == 0, result.stderr + curl_log = curl_log_path.read_text(encoding="utf-8") + assert "SHA256SUMS" not in curl_log + assert "intoto.jsonl" not in curl_log + assert "skipping checksum and provenance checks" in result.stdout + assert (home_path / ".strix/bin/strix").exists() + assert not (tmp_path / "gh.log").exists()