From 14b0aee28bd6bd56f1841f310875c2c0180a83c1 Mon Sep 17 00:00:00 2001 From: mohammad junayd <111487869+m-jay21@users.noreply.github.com> Date: Mon, 14 Sep 2026 17:35:05 +0400 Subject: [PATCH] fix(install): verify SHA256SUMS before extract Fail closed when the published checksum is missing or does not match the downloaded archive (#1267). --- scripts/install.sh | 73 +++++++++++++++++++++++++++++++++--- tests/test_install_script.py | 18 ++++++++- 2 files changed, 84 insertions(+), 7 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 471e7d89..3bcc0478 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -85,6 +85,8 @@ fi filename="$APP-${specific_version}-${target}${archive_ext}" url="https://github.com/$REPO/releases/download/v${specific_version}/$filename" +sums_name="SHA256SUMS" +sums_url="https://github.com/$REPO/releases/download/v${specific_version}/$sums_name" bundle_name="strix-${target}.intoto.jsonl" bundle_url="https://github.com/$REPO/releases/download/v${specific_version}/$bundle_name" SIGNER_WORKFLOW="$REPO/.github/workflows/build-release.yml" @@ -139,6 +141,55 @@ abort_unverified() { exit 1 } +# Fail-closed checksum check against the published SHA256SUMS manifest. +# Same-origin only (detects corruption / single-asset swap). Exact field +# match — do not grep the filename as a regex ('.' would be wild). +verify_checksum() { + local file=$1 + + if [ -n "${STRIX_INSTALL_SKIP_VERIFY:-}" ]; then + echo -e "${YELLOW}⚠ STRIX_INSTALL_SKIP_VERIFY set — skipping checksum verification (at your own risk).${NC}" + return 0 + fi + + local sha_cmd="" + if command -v sha256sum >/dev/null 2>&1; then + sha_cmd="sha256sum" + elif command -v shasum >/dev/null 2>&1; then + sha_cmd="shasum -a 256" + else + echo -e "${RED}✗ Neither 'sha256sum' nor 'shasum' is available; cannot verify integrity.${NC}" + abort_unverified + fi + + if [ ! -s "$sums_name" ]; then + echo -e "${RED}✗ Missing checksum manifest ${sums_name}.${NC}" + abort_unverified + fi + + echo -e "${MUTED}Verifying checksum...${NC}" + + local expected + expected=$(awk -v file="$file" ' + $2 == file || $2 == ("*" file) { print $1; exit } + ' "$sums_name") + if [ -z "$expected" ]; then + echo -e "${RED}✗ No SHA256SUMS entry for ${file}.${NC}" + abort_unverified + fi + + local actual + actual=$($sha_cmd "$file" | awk '{print $1}') + if [ "$actual" != "$expected" ]; then + echo -e "${RED}✗ Checksum mismatch for ${file}.${NC}" + echo -e "${MUTED}Expected: ${NC}$expected" + echo -e "${MUTED}Actual: ${NC}$actual" + abort_unverified + fi + + echo -e "${GREEN}✓ Checksum verified${NC}" +} + gh_can_verify_attestation() { command -v gh >/dev/null 2>&1 && gh attestation verify --help >/dev/null 2>&1 } @@ -231,13 +282,23 @@ download_and_install() { exit 1 fi - echo -e "${MUTED}Downloading provenance...${NC}" - if ! curl -sfL -o "$bundle_name" "$bundle_url" || [ ! -s "$bundle_name" ]; then - echo -e "${RED}✗ Failed to download provenance bundle.${NC}" - abort_unverified - fi + if [ -n "${STRIX_INSTALL_SKIP_VERIFY:-}" ]; then + echo -e "${YELLOW}⚠ STRIX_INSTALL_SKIP_VERIFY set — skipping checksum and provenance checks.${NC}" + else + echo -e "${MUTED}Downloading checksums...${NC}" + if ! curl -sfL -o "$sums_name" "$sums_url" || [ ! -s "$sums_name" ]; then + echo -e "${RED}✗ Failed to download checksum manifest.${NC}" + abort_unverified + fi + verify_checksum "$filename" - verify_provenance "$filename" "$bundle_name" + echo -e "${MUTED}Downloading provenance...${NC}" + if ! curl -sfL -o "$bundle_name" "$bundle_url" || [ ! -s "$bundle_name" ]; then + echo -e "${RED}✗ Failed to download provenance bundle.${NC}" + abort_unverified + fi + verify_provenance "$filename" "$bundle_name" + fi echo -e "${MUTED}Extracting...${NC}" if [ "$os" = "windows" ]; then diff --git a/tests/test_install_script.py b/tests/test_install_script.py index 2c9acc9b..2bde6820 100644 --- a/tests/test_install_script.py +++ b/tests/test_install_script.py @@ -62,16 +62,32 @@ exit 1 mock_bin / "curl", """#!/bin/sh output="" +url="" while [ "$#" -gt 0 ]; do if [ "$1" = "-o" ]; then output="$2" shift 2 continue fi + case "$1" in + http://*|https://*) url="$1" ;; + esac printf '%s\\n' "$1" >> "$STRIX_TEST_CURL_LOG" shift done -cp "$STRIX_TEST_ARCHIVE" "$output" +case "$url" in + */SHA256SUMS) + hash=$(sha256sum "$STRIX_TEST_ARCHIVE" | awk '{print $1}') + name=$(basename "$STRIX_TEST_ARCHIVE") + printf '%s %s\\n' "$hash" "$name" > "$output" + ;; + *.intoto.jsonl) + printf '{"test":true}\\n' > "$output" + ;; + *) + cp "$STRIX_TEST_ARCHIVE" "$output" + ;; +esac """, ) return mock_bin