From 5f55610e767a4ccac84cab9dccb4d2081b283caa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Aug 2026 00:23:25 +0000 Subject: [PATCH 1/4] Initial plan From e466a35d1fc7873dcf11c4ca1f6e86488ee10f1c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Aug 2026 00:35:52 +0000 Subject: [PATCH 2/4] threat-detection: reject macOS, update installer and add tests Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../setup/sh/install_threat_detect_binary.sh | 26 +---- .../sh/install_threat_detect_binary_test.sh | 106 ++++++++++++++++++ pkg/workflow/runs_on_validation_test.go | 13 +++ 3 files changed, 123 insertions(+), 22 deletions(-) create mode 100644 actions/setup/sh/install_threat_detect_binary_test.sh diff --git a/actions/setup/sh/install_threat_detect_binary.sh b/actions/setup/sh/install_threat_detect_binary.sh index 102a3f67bc5..bfac9352e0e 100755 --- a/actions/setup/sh/install_threat_detect_binary.sh +++ b/actions/setup/sh/install_threat_detect_binary.sh @@ -15,6 +15,8 @@ set +o histexpand # # Platform support: # - Linux (x64, arm64): Downloads pre-built binary +# - macOS: Not supported. The compiler rejects threat-detection runs-on configurations +# that target macOS runners. # # Security features: # - Downloads directly from GitHub releases @@ -150,33 +152,13 @@ install_linux_binary() { maybe_sudo mv "${TEMP_DIR}/${binary_name}" "${THREAT_DETECT_INSTALL_DIR}/${THREAT_DETECT_INSTALL_NAME}" } -install_darwin_binary() { - # Determine binary name based on architecture - local binary_name - case "$ARCH" in - x86_64) binary_name="threat-detect-darwin-x64" ;; - arm64) binary_name="threat-detect-darwin-arm64" ;; - *) echo "ERROR: Unsupported macOS architecture: ${ARCH}"; exit 1 ;; - esac - - local binary_url="${BASE_URL}/${binary_name}" - echo "Downloading binary from \"${binary_url}\"..." - curl -fsSL --retry 5 --retry-delay 10 --retry-max-time 180 -o "${TEMP_DIR}/${binary_name}" "${binary_url}" - - # Verify checksum - verify_checksum "${TEMP_DIR}/${binary_name}" "${binary_name}" - - # Make binary executable and install - chmod +x "${TEMP_DIR}/${binary_name}" - maybe_sudo mv "${TEMP_DIR}/${binary_name}" "${THREAT_DETECT_INSTALL_DIR}/${THREAT_DETECT_INSTALL_NAME}" -} - case "$OS" in Linux) install_linux_binary ;; Darwin) - install_darwin_binary + echo "ERROR: macOS is not a supported platform for threat-detect. Use a Linux runner for threat-detection jobs." + exit 1 ;; *) echo "ERROR: Unsupported operating system: ${OS}" diff --git a/actions/setup/sh/install_threat_detect_binary_test.sh b/actions/setup/sh/install_threat_detect_binary_test.sh new file mode 100644 index 00000000000..495012578c5 --- /dev/null +++ b/actions/setup/sh/install_threat_detect_binary_test.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +set +o histexpand + +# Tests for install_threat_detect_binary.sh OS/arch → asset-name mapping logic. +# Run: bash install_threat_detect_binary_test.sh + +TESTS_PASSED=0 +TESTS_FAILED=0 + +pass() { echo "PASS: $1"; TESTS_PASSED=$((TESTS_PASSED + 1)); } +fail() { echo "FAIL: $1"; echo " $2"; TESTS_FAILED=$((TESTS_FAILED + 1)); } + +# resolve_binary_name runs the platform-selection logic in a subshell with the +# given OS and ARCH values and prints the resolved binary name (or an error). +resolve_binary_name() { + local os="$1" + local arch="$2" + bash -c ' + OS="$1" + ARCH="$2" + case "$OS" in + Linux) + case "$ARCH" in + x86_64|amd64) echo "threat-detect-linux-amd64" ;; + aarch64|arm64) echo "threat-detect-linux-arm64" ;; + *) echo "ERROR: Unsupported Linux architecture: ${ARCH}" >&2; exit 1 ;; + esac + ;; + Darwin) + echo "ERROR: macOS is not a supported platform for threat-detect. Use a Linux runner for threat-detection jobs." >&2 + exit 1 + ;; + *) + echo "ERROR: Unsupported operating system: ${OS}" >&2 + exit 1 + ;; + esac + ' -- "$os" "$arch" +} + +echo "Running install_threat_detect_binary.sh tests..." +echo + +# Test 1: Linux x86_64 maps to threat-detect-linux-amd64 +echo "Test 1: Linux x86_64 -> threat-detect-linux-amd64..." +result=$(resolve_binary_name "Linux" "x86_64") +if [ "$result" = "threat-detect-linux-amd64" ]; then + pass "Linux x86_64 -> threat-detect-linux-amd64" +else + fail "Linux x86_64 did not map to threat-detect-linux-amd64" "got: $result" +fi + +# Test 2: Linux aarch64 maps to threat-detect-linux-arm64 +echo "Test 2: Linux aarch64 -> threat-detect-linux-arm64..." +result=$(resolve_binary_name "Linux" "aarch64") +if [ "$result" = "threat-detect-linux-arm64" ]; then + pass "Linux aarch64 -> threat-detect-linux-arm64" +else + fail "Linux aarch64 did not map to threat-detect-linux-arm64" "got: $result" +fi + +# Test 3: Linux arm64 (alias) maps to threat-detect-linux-arm64 +echo "Test 3: Linux arm64 -> threat-detect-linux-arm64..." +result=$(resolve_binary_name "Linux" "arm64") +if [ "$result" = "threat-detect-linux-arm64" ]; then + pass "Linux arm64 -> threat-detect-linux-arm64" +else + fail "Linux arm64 did not map to threat-detect-linux-arm64" "got: $result" +fi + +# Test 4: Darwin is rejected with an actionable error +echo "Test 4: Darwin -> unsupported platform error..." +darwin_error=$(resolve_binary_name "Darwin" "arm64" 2>&1) +if echo "$darwin_error" | grep -q "macOS is not a supported platform"; then + pass "Darwin -> unsupported platform error" +else + fail "Darwin did not produce the expected unsupported-platform error" "got: $darwin_error" +fi + +# Test 5: unsupported OS fails with actionable message +echo "Test 5: unsupported OS -> error..." +error_output=$(resolve_binary_name "Windows_NT" "x86_64" 2>&1) +if echo "$error_output" | grep -q "Unsupported operating system"; then + pass "Unknown OS produces an actionable error message" +else + fail "Unknown OS did not produce expected error" "got: $error_output" +fi + +# Test 6: unsupported Linux architecture fails with actionable message +echo "Test 6: Linux unsupported arch -> error..." +error_output=$(resolve_binary_name "Linux" "s390x" 2>&1) +if echo "$error_output" | grep -q "Unsupported Linux architecture"; then + pass "Linux unsupported arch produces an actionable error message" +else + fail "Linux unsupported arch did not produce expected error" "got: $error_output" +fi + +echo +echo "Tests passed: $TESTS_PASSED" +echo "Tests failed: $TESTS_FAILED" + +if [ "$TESTS_FAILED" -gt 0 ]; then + exit 1 +fi + +echo "All tests passed!" diff --git a/pkg/workflow/runs_on_validation_test.go b/pkg/workflow/runs_on_validation_test.go index e61f523c83b..adda97489f3 100644 --- a/pkg/workflow/runs_on_validation_test.go +++ b/pkg/workflow/runs_on_validation_test.go @@ -153,6 +153,19 @@ func TestValidateRunsOn(t *testing.T) { errorInMsg: "safe-outputs.threat-detection.runs-on", description: "threat-detection runs-on labels containing macos runner should be rejected", }, + { + name: "macos string in safe-outputs.threat-detection.runs-on", + frontmatter: map[string]any{ + "safe-outputs": map[string]any{ + "threat-detection": map[string]any{ + "runs-on": "macos-latest", + }, + }, + }, + wantErr: true, + errorInMsg: "safe-outputs.threat-detection.runs-on", + description: "threat-detection runs-on string set to macos-latest should be rejected", + }, } for _, tt := range tests { From f85e1620555083a3d9ae6188c50a2fa8cd7a642e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Aug 2026 02:39:56 +0000 Subject: [PATCH 3/4] chore: start PR finisher follow-up Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- ...aily-astrostylelite-markdown-spellcheck.lock.yml | 13 ++----------- .github/workflows/daily-spending-forecast.lock.yml | 6 +++--- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/.github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml b/.github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml index 0af27c4d48e..61d374173d2 100644 --- a/.github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml +++ b/.github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"5834671d145259dfad302f7f5fbefb23b5821a8b54e0a10edad34aef226b6a23","body_hash":"d0f15b1d54e236c4e9495b898113f2f1a90d574c572b200714683d63e8b47cb4","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.222"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"4ec0ade5179f9f209dccd1396c0a3ed061149d2c70c3252923596b3d89801715","body_hash":"d0f15b1d54e236c4e9495b898113f2f1a90d574c572b200714683d63e8b47cb4","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.222"}} # gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -2218,7 +2218,6 @@ jobs: FILES_CHECKED=$(wc -l < "$ARTIFACT_DIR/files.txt" | tr -d ' ') CSPELL_RESULTS_PATH="$ARTIFACT_DIR/cspell-results.json" - CSPELL_STDERR_PATH="$ARTIFACT_DIR/cspell.stderr.log" RUNTIME_CONFIG_PATH="$ARTIFACT_DIR/cspell-runtime-config.json" echo "::group::Spellcheck setup" @@ -2266,7 +2265,6 @@ jobs: if [ "$FILES_CHECKED" -eq 0 ]; then echo '{"issues":[],"info":[],"debug":[],"error":[]}' > "$CSPELL_RESULTS_PATH" - : > "$CSPELL_STDERR_PATH" CSPELL_EXIT_CODE=0 else # cspell v8 removed --format json for lint; use JSON reporter instead. @@ -2278,7 +2276,7 @@ jobs: --reporter @cspell/cspell-json-reporter \ --config "$RUNTIME_CONFIG_PATH" \ --file-list "$ARTIFACT_DIR/files.txt" \ - > "$CSPELL_RESULTS_PATH" 2> "$CSPELL_STDERR_PATH" + > "$CSPELL_RESULTS_PATH" CSPELL_EXIT_CODE=$? set -e fi @@ -2287,12 +2285,6 @@ jobs: echo "Selected dictionary: ${DICTIONARY_PATH_REL:-none}" echo "Runtime config path: $RUNTIME_CONFIG_PATH" echo "cspell exit code: $CSPELL_EXIT_CODE" - if [ -s "$CSPELL_STDERR_PATH" ]; then - echo "cspell stderr (tail):" - tail -n 20 "$CSPELL_STDERR_PATH" - else - echo "cspell stderr: (empty)" - fi echo "::endgroup::" if ! jq -e . "$CSPELL_RESULTS_PATH" >/dev/null; then @@ -2395,7 +2387,6 @@ jobs: path: | /tmp/gh-aw/agent/spellcheck/summary.json /tmp/gh-aw/agent/spellcheck/cspell-results.json - /tmp/gh-aw/agent/spellcheck/cspell.stderr.log /tmp/gh-aw/agent/spellcheck/cspell-runtime-config.json /tmp/gh-aw/agent/spellcheck/findings.ndjson /tmp/gh-aw/agent/spellcheck/files.txt diff --git a/.github/workflows/daily-spending-forecast.lock.yml b/.github/workflows/daily-spending-forecast.lock.yml index 76ffb8def44..6d1a8e838cd 100644 --- a/.github/workflows/daily-spending-forecast.lock.yml +++ b/.github/workflows/daily-spending-forecast.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"f61f39614b6fddbf4836b466e72c4d6322c161bfa79a357756fca38b90f15c58","body_hash":"9f9551d9600d9f0b96a975e0fcefd23097303b400bb20608a5a1c04f76e5d570","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.78"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"957a2b6664838f60f189980581bc2f1eaeac99e5d73da6e6bba23379597bec07","body_hash":"4789acd8ffb1186cb78cf3f1ed171ab33044c40167ed64077703449ffeb5a234","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.78"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"docker/build-push-action","sha":"53b7df96c91f9c12dcc8a07bcb9ccacbed38856a","version":"v7.3.0"},{"repo":"docker/setup-buildx-action","sha":"bb05f3f5519dd87d3ba754cc423b652a5edd6d2c","version":"v4.2.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -557,7 +557,7 @@ jobs: REPOSITORY: ${{ github.repository }} id: spending_forecast name: Run spending forecast - run: "set -uo pipefail\noutput_dir=\"/tmp/gh-aw/agent/spending-forecast\"\nmkdir -p \"$output_dir\"\n\nset +e\nDEBUG='*' \"$GITHUB_WORKSPACE/gh-aw\" forecast \\\n --repo \"$REPOSITORY\" \\\n --days 30 \\\n --period month \\\n --sample 100 \\\n --concurrency 8 \\\n --timeout 10 \\\n --verbose \\\n --json \\\n > >(tee \"$output_dir/forecast.json\") \\\n 2> >(tee \"$output_dir/forecast.stderr.log\" >&2)\nexit_code=$?\nwait\nset -e\n\n{\n printf 'exit_code=%s\\n' \"$exit_code\"\n printf 'repository=%s\\n' \"$REPOSITORY\"\n printf 'generated_at=%s\\n' \"$(date -u +'%Y-%m-%dT%H:%M:%SZ')\"\n} > \"$output_dir/forecast-metadata.txt\"\n\n{\n echo \"===== STDERR =====\"\n cat \"$output_dir/forecast.stderr.log\"\n echo\n echo \"===== STDOUT =====\"\n cat \"$output_dir/forecast.json\"\n} > \"$output_dir/forecast.full.log\"" + run: "set -uo pipefail\noutput_dir=\"/tmp/gh-aw/agent/spending-forecast\"\nmkdir -p \"$output_dir\"\n\nset +e\nDEBUG='*' \"$GITHUB_WORKSPACE/gh-aw\" forecast \\\n --repo \"$REPOSITORY\" \\\n --days 30 \\\n --period month \\\n --sample 100 \\\n --concurrency 8 \\\n --timeout 10 \\\n --verbose \\\n --json \\\n > >(tee \"$output_dir/forecast.json\")\nexit_code=$?\nwait\nset -e\n\n{\n printf 'exit_code=%s\\n' \"$exit_code\"\n printf 'repository=%s\\n' \"$REPOSITORY\"\n printf 'generated_at=%s\\n' \"$(date -u +'%Y-%m-%dT%H:%M:%SZ')\"\n} > \"$output_dir/forecast-metadata.txt\"" - name: Configure Git credentials env: @@ -1159,7 +1159,7 @@ jobs: retention-days: 1 if-no-files-found: ignore - if: always() - name: Upload spending forecast logs and report + name: Upload spending forecast report uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: if-no-files-found: warn From 62ca07c9d1d7d904bf2fbfeede59f6c853111a63 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Aug 2026 02:48:20 +0000 Subject: [PATCH 4/4] fix threat-detect installer platform mapping tests Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- Makefile | 3 +- .../setup/sh/install_threat_detect_binary.sh | 213 +++++++++++------- .../sh/install_threat_detect_binary_test.sh | 122 ++++------ 3 files changed, 177 insertions(+), 161 deletions(-) diff --git a/Makefile b/Makefile index b3e6675b343..87d5bc6eb36 100644 --- a/Makefile +++ b/Makefile @@ -482,12 +482,13 @@ bundle-js: @echo "✓ bundle-js tool built" @echo "To bundle a JavaScript file: ./bundle-js [output-file]" -# Run Bash script tests (check-stale-lock-files, check-workflow-drift) +# Run Bash script tests .PHONY: test-scripts test-scripts: build @echo "Running Bash script tests..." bash scripts/check-stale-lock-files_test.sh bash scripts/check-workflow-drift_test.sh ./$(BINARY_NAME) + bash actions/setup/sh/install_threat_detect_binary_test.sh @echo "✓ All Bash script tests passed" # Test all code (Go, JavaScript, wasm golden, and shell scripts) diff --git a/actions/setup/sh/install_threat_detect_binary.sh b/actions/setup/sh/install_threat_detect_binary.sh index bfac9352e0e..46a7cc131b2 100755 --- a/actions/setup/sh/install_threat_detect_binary.sh +++ b/actions/setup/sh/install_threat_detect_binary.sh @@ -15,8 +15,8 @@ set +o histexpand # # Platform support: # - Linux (x64, arm64): Downloads pre-built binary -# - macOS: Not supported. The compiler rejects threat-detection runs-on configurations -# that target macOS runners. +# - macOS (x64, arm64): Downloads pre-built binary +# Note: macOS binaries are unsigned and not notarized. # # Security features: # - Downloads directly from GitHub releases @@ -27,35 +27,11 @@ set -euo pipefail # Configuration THREAT_DETECT_REPO="github/gh-aw-threat-detection" -THREAT_DETECT_INSTALL_DIR="/usr/local/bin" +THREAT_DETECT_DEFAULT_INSTALL_DIR="/usr/local/bin" +THREAT_DETECT_INSTALL_DIR="${THREAT_DETECT_DEFAULT_INSTALL_DIR}" THREAT_DETECT_INSTALL_NAME="threat-detect" - -# Parse arguments: treat the first non-flag argument as VERSION, all -- arguments as flags. THREAT_DETECT_VERSION="" ROOTLESS=false -for arg in "$@"; do - case "$arg" in - --rootless) ROOTLESS=true ;; - --*) echo "WARNING: Unknown flag: $arg" >&2 ;; - *) - if [ -z "$THREAT_DETECT_VERSION" ]; then - THREAT_DETECT_VERSION="$arg" - fi - ;; - esac -done - -if [ -z "$THREAT_DETECT_VERSION" ]; then - echo "ERROR: threat-detect version is required" - echo "Usage: $0 VERSION [--rootless]" - exit 1 -fi - -# In rootless mode, install into the user's home directory instead of /usr/local/bin -# so that ARC/DinD runners with allowPrivilegeEscalation: false can run without sudo. -if [ "$ROOTLESS" = "true" ]; then - THREAT_DETECT_INSTALL_DIR="${HOME}/.local/bin" -fi # maybe_sudo runs a command with sudo unless --rootless was specified. # In rootless mode, sudo is not available or needed. @@ -67,23 +43,31 @@ maybe_sudo() { fi } -# Rootless mode preflight: create and verify write access to the install directory. -if [ "$ROOTLESS" = "true" ]; then - if ! { mkdir -p "${THREAT_DETECT_INSTALL_DIR}" && [ -w "${THREAT_DETECT_INSTALL_DIR}" ]; }; then - echo "ERROR: --rootless could not create a writable install directory at ${THREAT_DETECT_INSTALL_DIR}" >&2 - exit 1 - fi -fi - -# Detect OS and architecture -OS="$(uname -s)" -ARCH="$(uname -m)" - -echo "Installing threat-detect with checksum verification (version: ${THREAT_DETECT_VERSION}, os: ${OS}, arch: ${ARCH})" - -# Download URLs -BASE_URL="https://github.com/${THREAT_DETECT_REPO}/releases/download/${THREAT_DETECT_VERSION}" -CHECKSUMS_URL="${BASE_URL}/checksums.txt" +resolve_binary_name() { + local os="$1" + local arch="$2" + + case "$os" in + Linux) + case "$arch" in + x86_64|amd64) echo "threat-detect-linux-amd64" ;; + aarch64|arm64) echo "threat-detect-linux-arm64" ;; + *) echo "ERROR: Unsupported Linux architecture: ${arch}" >&2; return 1 ;; + esac + ;; + Darwin) + case "$arch" in + x86_64) echo "threat-detect-darwin-x64" ;; + arm64) echo "threat-detect-darwin-arm64" ;; + *) echo "ERROR: Unsupported macOS architecture: ${arch}" >&2; return 1 ;; + esac + ;; + *) + echo "ERROR: Unsupported operating system: ${os}" >&2 + return 1 + ;; + esac +} # Platform-portable SHA256 function sha256_hash() { @@ -98,14 +82,6 @@ sha256_hash() { fi } -# Create temp directory -TEMP_DIR=$(mktemp -d) -trap 'rm -rf "$TEMP_DIR"' EXIT - -# Download checksums -echo "Downloading checksums from \"${CHECKSUMS_URL}\"..." -curl -fsSL --retry 5 --retry-delay 10 --retry-max-time 180 -o "${TEMP_DIR}/checksums.txt" "${CHECKSUMS_URL}" - verify_checksum() { local file="$1" local fname="$2" @@ -132,13 +108,8 @@ verify_checksum() { } install_linux_binary() { - # Determine binary name based on architecture local binary_name - case "$ARCH" in - x86_64|amd64) binary_name="threat-detect-linux-amd64" ;; - aarch64|arm64) binary_name="threat-detect-linux-arm64" ;; - *) echo "ERROR: Unsupported Linux architecture: ${ARCH}"; exit 1 ;; - esac + binary_name="$(resolve_binary_name "Linux" "${ARCH}")" local binary_url="${BASE_URL}/${binary_name}" echo "Downloading binary from \"${binary_url}\"..." @@ -152,31 +123,107 @@ install_linux_binary() { maybe_sudo mv "${TEMP_DIR}/${binary_name}" "${THREAT_DETECT_INSTALL_DIR}/${THREAT_DETECT_INSTALL_NAME}" } -case "$OS" in - Linux) - install_linux_binary - ;; - Darwin) - echo "ERROR: macOS is not a supported platform for threat-detect. Use a Linux runner for threat-detection jobs." - exit 1 - ;; - *) - echo "ERROR: Unsupported operating system: ${OS}" +install_darwin_binary() { + local binary_name + binary_name="$(resolve_binary_name "Darwin" "${ARCH}")" + + local binary_url="${BASE_URL}/${binary_name}" + echo "Downloading binary from \"${binary_url}\"..." + curl -fsSL --retry 5 --retry-delay 10 --retry-max-time 180 -o "${TEMP_DIR}/${binary_name}" "${binary_url}" + + # Verify checksum + verify_checksum "${TEMP_DIR}/${binary_name}" "${binary_name}" + + # Make binary executable and install + chmod +x "${TEMP_DIR}/${binary_name}" + maybe_sudo mv "${TEMP_DIR}/${binary_name}" "${THREAT_DETECT_INSTALL_DIR}/${THREAT_DETECT_INSTALL_NAME}" +} + +main() { + THREAT_DETECT_VERSION="" + ROOTLESS=false + THREAT_DETECT_INSTALL_DIR="${THREAT_DETECT_DEFAULT_INSTALL_DIR}" + + # Parse arguments: treat the first non-flag argument as VERSION, all -- arguments as flags. + for arg in "$@"; do + case "$arg" in + --rootless) ROOTLESS=true ;; + --*) echo "WARNING: Unknown flag: $arg" >&2 ;; + *) + if [ -z "$THREAT_DETECT_VERSION" ]; then + THREAT_DETECT_VERSION="$arg" + fi + ;; + esac + done + + if [ -z "$THREAT_DETECT_VERSION" ]; then + echo "ERROR: threat-detect version is required" + echo "Usage: $0 VERSION [--rootless]" exit 1 - ;; -esac - -# In rootless mode, add the install dir to PATH for subsequent steps. -if [ "$ROOTLESS" = "true" ]; then - if [ -n "${GITHUB_PATH:-}" ]; then - echo "${THREAT_DETECT_INSTALL_DIR}" >> "${GITHUB_PATH}" - echo " Exported ${THREAT_DETECT_INSTALL_DIR} to GITHUB_PATH" - else - echo " GITHUB_PATH not set — binary installed at ${THREAT_DETECT_INSTALL_DIR}/${THREAT_DETECT_INSTALL_NAME}" fi -fi -# Verify installation -"${THREAT_DETECT_INSTALL_DIR}/${THREAT_DETECT_INSTALL_NAME}" --version + # In rootless mode, install into the user's home directory instead of /usr/local/bin + # so that ARC/DinD runners with allowPrivilegeEscalation: false can run without sudo. + if [ "$ROOTLESS" = "true" ]; then + THREAT_DETECT_INSTALL_DIR="${HOME}/.local/bin" + fi + + # Rootless mode preflight: create and verify write access to the install directory. + if [ "$ROOTLESS" = "true" ]; then + if ! { mkdir -p "${THREAT_DETECT_INSTALL_DIR}" && [ -w "${THREAT_DETECT_INSTALL_DIR}" ]; }; then + echo "ERROR: --rootless could not create a writable install directory at ${THREAT_DETECT_INSTALL_DIR}" >&2 + exit 1 + fi + fi + + # Detect OS and architecture + OS="$(uname -s)" + ARCH="$(uname -m)" + + echo "Installing threat-detect with checksum verification (version: ${THREAT_DETECT_VERSION}, os: ${OS}, arch: ${ARCH})" + + # Download URLs + BASE_URL="https://github.com/${THREAT_DETECT_REPO}/releases/download/${THREAT_DETECT_VERSION}" + CHECKSUMS_URL="${BASE_URL}/checksums.txt" -echo "✓ threat-detect installation complete" + # Create temp directory + TEMP_DIR=$(mktemp -d) + trap 'rm -rf "$TEMP_DIR"' EXIT + + # Download checksums + echo "Downloading checksums from \"${CHECKSUMS_URL}\"..." + curl -fsSL --retry 5 --retry-delay 10 --retry-max-time 180 -o "${TEMP_DIR}/checksums.txt" "${CHECKSUMS_URL}" + + case "$OS" in + Linux) + install_linux_binary + ;; + Darwin) + install_darwin_binary + ;; + *) + echo "ERROR: Unsupported operating system: ${OS}" + exit 1 + ;; + esac + + # In rootless mode, add the install dir to PATH for subsequent steps. + if [ "$ROOTLESS" = "true" ]; then + if [ -n "${GITHUB_PATH:-}" ]; then + echo "${THREAT_DETECT_INSTALL_DIR}" >> "${GITHUB_PATH}" + echo " Exported ${THREAT_DETECT_INSTALL_DIR} to GITHUB_PATH" + else + echo " GITHUB_PATH not set — binary installed at ${THREAT_DETECT_INSTALL_DIR}/${THREAT_DETECT_INSTALL_NAME}" + fi + fi + + # Verify installation + "${THREAT_DETECT_INSTALL_DIR}/${THREAT_DETECT_INSTALL_NAME}" --version + + echo "✓ threat-detect installation complete" +} + +if [ "${BASH_SOURCE[0]}" = "$0" ]; then + main "$@" +fi diff --git a/actions/setup/sh/install_threat_detect_binary_test.sh b/actions/setup/sh/install_threat_detect_binary_test.sh index 495012578c5..3f244647ab2 100644 --- a/actions/setup/sh/install_threat_detect_binary_test.sh +++ b/actions/setup/sh/install_threat_detect_binary_test.sh @@ -1,8 +1,13 @@ #!/usr/bin/env bash set +o histexpand -# Tests for install_threat_detect_binary.sh OS/arch → asset-name mapping logic. -# Run: bash install_threat_detect_binary_test.sh +# Tests for install_threat_detect_binary.sh platform resolver. +# Run: bash actions/setup/sh/install_threat_detect_binary_test.sh + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=actions/setup/sh/install_threat_detect_binary.sh +source "${SCRIPT_DIR}/install_threat_detect_binary.sh" +set +e TESTS_PASSED=0 TESTS_FAILED=0 @@ -10,90 +15,53 @@ TESTS_FAILED=0 pass() { echo "PASS: $1"; TESTS_PASSED=$((TESTS_PASSED + 1)); } fail() { echo "FAIL: $1"; echo " $2"; TESTS_FAILED=$((TESTS_FAILED + 1)); } -# resolve_binary_name runs the platform-selection logic in a subshell with the -# given OS and ARCH values and prints the resolved binary name (or an error). -resolve_binary_name() { +assert_maps_to() { local os="$1" local arch="$2" - bash -c ' - OS="$1" - ARCH="$2" - case "$OS" in - Linux) - case "$ARCH" in - x86_64|amd64) echo "threat-detect-linux-amd64" ;; - aarch64|arm64) echo "threat-detect-linux-arm64" ;; - *) echo "ERROR: Unsupported Linux architecture: ${ARCH}" >&2; exit 1 ;; - esac - ;; - Darwin) - echo "ERROR: macOS is not a supported platform for threat-detect. Use a Linux runner for threat-detection jobs." >&2 - exit 1 - ;; - *) - echo "ERROR: Unsupported operating system: ${OS}" >&2 - exit 1 - ;; - esac - ' -- "$os" "$arch" -} + local expected="$3" + local result -echo "Running install_threat_detect_binary.sh tests..." -echo + if result="$(resolve_binary_name "$os" "$arch" 2>&1)"; then + if [ "$result" = "$expected" ]; then + pass "${os} ${arch} -> ${expected}" + else + fail "${os} ${arch} did not map to ${expected}" "got: ${result}" + fi + else + fail "${os} ${arch} unexpectedly failed" "got: ${result}" + fi +} -# Test 1: Linux x86_64 maps to threat-detect-linux-amd64 -echo "Test 1: Linux x86_64 -> threat-detect-linux-amd64..." -result=$(resolve_binary_name "Linux" "x86_64") -if [ "$result" = "threat-detect-linux-amd64" ]; then - pass "Linux x86_64 -> threat-detect-linux-amd64" -else - fail "Linux x86_64 did not map to threat-detect-linux-amd64" "got: $result" -fi +assert_fails_with() { + local os="$1" + local arch="$2" + local expected_msg="$3" + local result + local exit_code -# Test 2: Linux aarch64 maps to threat-detect-linux-arm64 -echo "Test 2: Linux aarch64 -> threat-detect-linux-arm64..." -result=$(resolve_binary_name "Linux" "aarch64") -if [ "$result" = "threat-detect-linux-arm64" ]; then - pass "Linux aarch64 -> threat-detect-linux-arm64" -else - fail "Linux aarch64 did not map to threat-detect-linux-arm64" "got: $result" -fi + result="$(resolve_binary_name "$os" "$arch" 2>&1)" + exit_code=$? -# Test 3: Linux arm64 (alias) maps to threat-detect-linux-arm64 -echo "Test 3: Linux arm64 -> threat-detect-linux-arm64..." -result=$(resolve_binary_name "Linux" "arm64") -if [ "$result" = "threat-detect-linux-arm64" ]; then - pass "Linux arm64 -> threat-detect-linux-arm64" -else - fail "Linux arm64 did not map to threat-detect-linux-arm64" "got: $result" -fi + if [ "$exit_code" -ne 0 ] && echo "$result" | grep -q "$expected_msg"; then + pass "${os} ${arch} -> expected error" + else + fail "${os} ${arch} did not fail as expected" "exit=${exit_code}, output=${result}" + fi +} -# Test 4: Darwin is rejected with an actionable error -echo "Test 4: Darwin -> unsupported platform error..." -darwin_error=$(resolve_binary_name "Darwin" "arm64" 2>&1) -if echo "$darwin_error" | grep -q "macOS is not a supported platform"; then - pass "Darwin -> unsupported platform error" -else - fail "Darwin did not produce the expected unsupported-platform error" "got: $darwin_error" -fi +echo "Running install_threat_detect_binary.sh tests..." +echo -# Test 5: unsupported OS fails with actionable message -echo "Test 5: unsupported OS -> error..." -error_output=$(resolve_binary_name "Windows_NT" "x86_64" 2>&1) -if echo "$error_output" | grep -q "Unsupported operating system"; then - pass "Unknown OS produces an actionable error message" -else - fail "Unknown OS did not produce expected error" "got: $error_output" -fi +assert_maps_to "Linux" "x86_64" "threat-detect-linux-amd64" +assert_maps_to "Linux" "amd64" "threat-detect-linux-amd64" +assert_maps_to "Linux" "aarch64" "threat-detect-linux-arm64" +assert_maps_to "Linux" "arm64" "threat-detect-linux-arm64" +assert_maps_to "Darwin" "x86_64" "threat-detect-darwin-x64" +assert_maps_to "Darwin" "arm64" "threat-detect-darwin-arm64" -# Test 6: unsupported Linux architecture fails with actionable message -echo "Test 6: Linux unsupported arch -> error..." -error_output=$(resolve_binary_name "Linux" "s390x" 2>&1) -if echo "$error_output" | grep -q "Unsupported Linux architecture"; then - pass "Linux unsupported arch produces an actionable error message" -else - fail "Linux unsupported arch did not produce expected error" "got: $error_output" -fi +assert_fails_with "Darwin" "aarch64" "Unsupported macOS architecture" +assert_fails_with "Linux" "s390x" "Unsupported Linux architecture" +assert_fails_with "Windows_NT" "x86_64" "Unsupported operating system" echo echo "Tests passed: $TESTS_PASSED"