From cb2636900c2b38207e05532fd1128d52e21bad6a Mon Sep 17 00:00:00 2001 From: BathreeNode <101283333+batuhankocyigit@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:49:30 +0300 Subject: [PATCH 1/4] fix(arcup): guard install script's own curl call against same exit-code bug --- arcup/arcup | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arcup/arcup b/arcup/arcup index 3590cd36..46ccb1f5 100755 --- a/arcup/arcup +++ b/arcup/arcup @@ -324,7 +324,7 @@ download_file() { return fi - if ! curl_with_headers -#fL --max-time 900 -o "$output_path" "$url"; then + if ! curl_with_headers -#fL --max-time 900 -o "$output_path" "$url" || [[ ! -s "$output_path" ]]; then rm -f "$output_path" download_error "$tag" "$filename" "$url" fi @@ -350,7 +350,7 @@ download_file_with_github_api() { download_url=$(resolve_github_asset_download_url "$asset_api_url") || return 1 if curl "${CURL_RETRY_ARGS[@]}" -fL --max-time 900 -o "$output_path" "$download_url" >/dev/null 2>&1; then - [[ -f "$output_path" ]] && return 0 + [[ -s "$output_path" ]] && return 0 fi rm -f "$output_path" @@ -374,7 +374,7 @@ download_file_with_gh() { --dir "$output_dir" \ --clobber \ >/dev/null 2>&1; then - [[ -f "$output_path" ]] && return 0 + [[ -s "$output_path" ]] && return 0 fi rm -f "$output_path" From 384833d7ff6aef01c1eb2bb03384e4258a78d675 Mon Sep 17 00:00:00 2001 From: BathreeNode <101283333+batuhankocyigit@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:54:46 +0300 Subject: [PATCH 2/4] Update install --- arcup/install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arcup/install b/arcup/install index b9a8d2bf..6a98ecb5 100755 --- a/arcup/install +++ b/arcup/install @@ -72,7 +72,7 @@ main() { error "curl not found. Please install curl." fi - if ! curl_with_headers -#fL --max-time 300 -o "$_INSTALL_TMP_FILE" "$ARCUP_URL" ; then + if ! curl_with_headers -#fL --max-time 300 -o "$_INSTALL_TMP_FILE" "$ARCUP_URL" || [[ ! -s "$_INSTALL_TMP_FILE" ]]; then error "failed to download arcup from '$ARCUP_URL'" fi From bdcfc7d5bbf7d420270237b94407ab594eb02cb5 Mon Sep 17 00:00:00 2001 From: BathreeNode <101283333+batuhankocyigit@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:10:00 +0300 Subject: [PATCH 3/4] test(arcup): add regression tests for curl 8.14.x silent-success bug --- arcup/test_arcup.sh | 211 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) diff --git a/arcup/test_arcup.sh b/arcup/test_arcup.sh index 49021191..ec377955 100755 --- a/arcup/test_arcup.sh +++ b/arcup/test_arcup.sh @@ -641,6 +641,162 @@ EOF pass "download_file falls back to curl" } +# --- Regression tests for the curl 8.14.0/8.14.1 exit-code bug (issue #309) --- +# On these versions, curl with --retry set but not triggered (permanent +# failures like 404) can exit 0 while leaving an empty/absent output file. +# These tests simulate exactly that shape: exit 0, empty file. + +test_download_file_rejects_empty_success_from_curl_fallback() { + local fakebin="$TEST_TMP/empty-success-fakebin" + local output_dir="$TEST_TMP/empty-success-output" + local asset_name="arc-node-v1.2.3-aarch64-apple-darwin.tar.gz" + + mkdir -p "$fakebin" "$output_dir" + + # gh not available, so download_file falls through to the bare curl call. + cat > "$fakebin/gh" <<'EOF' +#!/usr/bin/env bash +exit 1 +EOF + chmod 755 "$fakebin/gh" + + cat > "$fakebin/curl" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +out="" +url="" +while [[ $# -gt 0 ]]; do + case "$1" in + -o) + out="$2" + shift 2 + ;; + --retry | --retry-delay | --connect-timeout | --max-time) + shift 2 + ;; + -*) + shift + ;; + *) + url="$1" + shift + ;; + esac +done + +: > "$out" +exit 0 +EOF + chmod 755 "$fakebin/curl" + + if ( + PATH="$fakebin:$PATH" + GITHUB_AUTH_TOKEN="" + CURL_HEADERS=() + REPO="test/arc-node" + download_file "v1.2.3" "$asset_name" "$output_dir" + ) >"$TEST_TMP/empty-success.out" 2>&1; then + cat "$TEST_TMP/empty-success.out" >&2 + fail "download_file rejects curl's empty-file success (8.14.x bug)" + fi + + if [[ -e "$output_dir/$asset_name" ]]; then + fail "download_file must not leave an empty file behind" + fi + + pass "download_file rejects curl's empty-file success (8.14.x bug)" +} + +test_download_file_with_github_api_rejects_empty_success() { + local fakebin="$TEST_TMP/empty-token-api-fakebin" + local output_dir="$TEST_TMP/empty-token-api-output" + local asset_name="arc-node-v1.2.3-aarch64-apple-darwin.tar.gz" + + mkdir -p "$fakebin" "$output_dir" + + cat > "$fakebin/curl" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +out="" +dump="" +url="" +while [[ $# -gt 0 ]]; do + case "$1" in + -o) + out="$2" + shift 2 + ;; + -D) + dump="$2" + shift 2 + ;; + -H | --retry | --retry-delay | --connect-timeout | --max-time) + shift 2 + ;; + -*) + shift + ;; + *) + url="$1" + shift + ;; + esac +done + +case "$url" in + *api.github.com/repos/test/arc-node/releases/tags/v1.2.3) + data='{"assets":[{"url":"https://api.github.com/repos/test/arc-node/releases/assets/123","name":"arc-node-v1.2.3-aarch64-apple-darwin.tar.gz"}]}' + ;; + *api.github.com/repos/test/arc-node/releases/assets/123) + if [[ "$dump" == "-" ]]; then + printf 'HTTP/1.1 302 Found\r\nLocation: https://objects.example/arc-node-v1.2.3-aarch64-apple-darwin.tar.gz\r\n\r\n' + exit 0 + fi + exit 22 + ;; + *objects.example/arc-node-v1.2.3-aarch64-apple-darwin.tar.gz) + # curl 8.14.x bug: exits 0 on the final asset download even + # though the permanent-failure response body is empty. + : > "$out" + exit 0 + ;; + *) + printf 'unexpected curl URL: %s\n' "$url" >&2 + exit 22 + ;; +esac + +if [[ -n "$out" ]]; then + printf '%s\n' "$data" > "$out" +else + printf '%s\n' "$data" +fi +EOF + chmod 755 "$fakebin/curl" + + ( + PATH="$fakebin:$PATH" + GITHUB_AUTH_TOKEN="test-token" + CURL_HEADERS=() + REPO="test/arc-node" + if download_file_with_github_api "v1.2.3" "$asset_name" "$output_dir"; then + exit 1 + fi + exit 0 + ) >"$TEST_TMP/empty-token-api.out" 2>&1 || { + cat "$TEST_TMP/empty-token-api.out" >&2 + fail "download_file_with_github_api rejects curl's empty-file success (8.14.x bug)" + } + + if [[ -e "$output_dir/$asset_name" ]]; then + fail "download_file_with_github_api must not leave an empty file behind" + fi + + pass "download_file_with_github_api rejects curl's empty-file success (8.14.x bug)" +} + test_fixture_install_matrix() { local fixture_dir="$TEST_TMP/fixture" local fakebin="$TEST_TMP/fakebin" @@ -810,6 +966,58 @@ test_install_binary_rejects_symlink() { pass "install_binary rejects symlink" } +# --- Regression test for arcup/install's own curl call (issue #309) --- + +test_install_rejects_empty_arcup_download() { + local fakebin="$TEST_TMP/install-empty-fakebin" + local arc_dir="$TEST_TMP/install-empty-arc-dir" + + mkdir -p "$fakebin" + + cat > "$fakebin/curl" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +out="" +while [[ $# -gt 0 ]]; do + case "$1" in + -o) + out="$2" + shift 2 + ;; + --retry | --retry-delay | --connect-timeout | --max-time) + shift 2 + ;; + -*) + shift + ;; + *) + shift + ;; + esac +done + +: > "$out" +exit 0 +EOF + chmod 755 "$fakebin/curl" + + if ( + PATH="$fakebin:$PATH" + ARC_DIR="$arc_dir" + bash "$ROOT_DIR/arcup/install" + ) >"$TEST_TMP/install-empty.out" 2>&1; then + cat "$TEST_TMP/install-empty.out" >&2 + fail "install rejects empty arcup download (8.14.x bug)" + fi + + if [[ -x "$arc_dir/bin/arcup" ]]; then + fail "install must not leave an empty executable behind" + fi + + pass "install rejects empty arcup download (8.14.x bug)" +} + test_version_normalization test_version_comparison test_target_mapping @@ -823,7 +1031,10 @@ test_latest_version_retries_anonymous_after_token_failure test_latest_version_redacts_authenticated_failure test_download_file_uses_gh_when_available test_download_file_falls_back_to_curl +test_download_file_rejects_empty_success_from_curl_fallback +test_download_file_with_github_api_rejects_empty_success test_fixture_install_matrix test_archive_path_traversal_fails test_archive_link_entries_fail test_install_binary_rejects_symlink +test_install_rejects_empty_arcup_download From 682d6a0ecb74af5b7232b1df580ea1882181fa11 Mon Sep 17 00:00:00 2001 From: BathreeNode <101283333+batuhankocyigit@users.noreply.github.com> Date: Thu, 3 Sep 2026 14:07:11 +0300 Subject: [PATCH 4/4] Bump installer version to 0.2.1 --- arcup/arcup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arcup/arcup b/arcup/arcup index 46ccb1f5..9cda8a9d 100755 --- a/arcup/arcup +++ b/arcup/arcup @@ -6,7 +6,7 @@ set -euo pipefail # NOTE: if you make modifications to this script, please increment the version number. # WARNING: the SemVer pattern: major.minor.patch must be followed as we use it to determine if the script is up to date. -ARCUP_INSTALLER_VERSION="0.2.0" +ARCUP_INSTALLER_VERSION="0.2.1" REPO="${ARC_REPO:-circlefin/arc-node}" if [[ -n "${ARC_REPO:-}" ]] && [[ ! "$ARC_REPO" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then