diff --git a/CHANGELOG.md b/CHANGELOG.md index 541e2a10..cc8711b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ - Floating major tag for the GitHub Action: the release process now force-moves `v0` to each release, so workflows can pin `TypedDevs/bashunit@v0` to track the latest release within a major (#700) - `bashunit init` now scaffolds a `.github/workflows/tests.yml` CI workflow using the official action (existing files are left untouched) (#702) +### Changed +- `install.sh` now verifies the release checksum by default (set `BASHUNIT_VERIFY_CHECKSUM=false` to opt out); it soft-skips with a warning when a checksum asset is unavailable unless verification was explicitly requested (#703) + ## [0.38.0](https://github.com/TypedDevs/bashunit/compare/0.37.0...0.38.0) - 2026-06-07 ### Added diff --git a/docs/installation.md b/docs/installation.md index 49bc4531..3d39c8cb 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -30,6 +30,13 @@ curl -s https://bashunit.typeddevs.com/install.sh | bash This will create a file inside a lib folder, such as `lib/bashunit`. +::: tip Automatic checksum verification +`install.sh` verifies the download against the release `checksum` asset by default and +aborts on a mismatch, so a tampered or corrupted download never lands. Set +`BASHUNIT_VERIFY_CHECKSUM=false` to opt out (e.g. for old releases published before +checksum assets existed). The manual check below is only needed when you opt out. +::: + #### Verify ```bash-vue diff --git a/install.sh b/install.sh index 76514e43..79dcff30 100755 --- a/install.sh +++ b/install.sh @@ -24,10 +24,31 @@ function compute_sha256() { # Verify the downloaded 'bashunit' file against the release 'checksum' asset. # Arguments: $1 - the bashunit download URL. Exits 1 (and removes the binary) # on any failure so a tampered or unverifiable download never looks successful. +# Handle an inability to verify (missing sha256 tool or checksum asset). +# Strict (fails) when the user explicitly opted in; a soft warning otherwise, +# so installing older releases published before checksum assets existed keeps +# working while a tampered binary (checksum mismatch) is always rejected. +function cannot_verify() { + local reason=$1 + if [ "${VERIFY_CHECKSUM_EXPLICIT:-false}" = "true" ]; then + echo "Error: $reason" >&2 + rm -f bashunit + exit 1 + fi + echo "> Skipping checksum verification: $reason" >&2 +} + function verify_checksum() { local url=$1 local checksum_url="${url%/bashunit}/checksum" + local actual + actual=$(compute_sha256 bashunit) + if [ -z "$actual" ]; then + cannot_verify "no sha256 tool (shasum/sha256sum) available to verify checksum" + return + fi + local expected if command -v curl >/dev/null 2>&1; then expected=$(curl -fsSL --retry 3 --retry-delay 2 "$checksum_url" 2>/dev/null | awk '{print $1}') @@ -36,17 +57,8 @@ function verify_checksum() { fi if [ -z "$expected" ]; then - echo "Error: could not download checksum from $checksum_url" >&2 - rm -f bashunit - exit 1 - fi - - local actual - actual=$(compute_sha256 bashunit) - if [ -z "$actual" ]; then - echo "Error: no sha256 tool (shasum/sha256sum) available to verify checksum" >&2 - rm -f bashunit - exit 1 + cannot_verify "could not download checksum from $checksum_url" + return fi if [ "$actual" != "$expected" ]; then @@ -57,7 +69,7 @@ function verify_checksum() { exit 1 fi - echo "> Checksum verified ($actual)" + echo "> Checksum verified ($actual)" >&2 } function build_and_install_beta() { @@ -108,7 +120,7 @@ function install() { exit 1 fi - if [ "${BASHUNIT_VERIFY_CHECKSUM:-false}" = "true" ]; then + if [ "$VERIFY_CHECKSUM" = "true" ]; then verify_checksum "$url" fi @@ -123,6 +135,15 @@ function install() { DIR="lib" VERSION="latest" +# Checksum verification is on by default. Track whether the user set it +# explicitly so we can be strict on opt-in and lenient on the default. +if [ -n "${BASHUNIT_VERIFY_CHECKSUM+x}" ]; then + VERIFY_CHECKSUM_EXPLICIT="true" +else + VERIFY_CHECKSUM_EXPLICIT="false" +fi +VERIFY_CHECKSUM="${BASHUNIT_VERIFY_CHECKSUM:-true}" + function is_version() { regex_match "$1" '^[0-9]+\.[0-9]+\.[0-9]+$' || [[ "$1" == "latest" || "$1" == "beta" ]] } diff --git a/tests/acceptance/install_test.sh b/tests/acceptance/install_test.sh index 1d163ce8..ffa6e8b2 100644 --- a/tests/acceptance/install_test.sh +++ b/tests/acceptance/install_test.sh @@ -151,6 +151,27 @@ function test_install_verifies_checksum_when_enabled() { "$(./tmp_install/bashunit --version)" } +function test_install_verifies_checksum_by_default() { + if [[ "$ACTIVE_INTERNET" -eq 1 ]]; then + bashunit::skip "no internet connection" && return + fi + if [[ "$HAS_DOWNLOADER" -eq 0 ]]; then + bashunit::skip "curl or wget not installed" && return + fi + if ! command -v shasum >/dev/null 2>&1 && ! command -v sha256sum >/dev/null 2>&1; then + bashunit::skip "no sha256 tool available" && return + fi + + local output + output="$(./install.sh tmp_install 0.38.0 2>&1)" + + if [ ! -f "./tmp_install/bashunit" ]; then + bashunit::skip "transient download failure" && return + fi + assert_contains "Checksum verified" "$output" + assert_file_exists "./tmp_install/bashunit" +} + function test_install_downloads_the_given_version() { if [[ "$ACTIVE_INTERNET" -eq 1 ]]; then bashunit::skip "no internet connection" && return