From 834cbf3cab8c015e9decd371deed9bdbc83076c2 Mon Sep 17 00:00:00 2001 From: Riccardo Schirone Date: Mon, 26 Jan 2026 15:12:49 +0100 Subject: [PATCH] ci: improve GitHub Actions workflow security and compatibility - Replace deprecated `uv pip install --isolated` with direct `uv pip install` - Fix zizmor template-injection warnings by using env variables instead of direct template expressions in shell commands - Quote $GITHUB_OUTPUT for proper shell escaping Co-Authored-By: Claude Opus 4.5 --- .github/workflows/component-integration.yml | 21 ++++++---- .github/workflows/system-integration.yml | 46 ++++++++++----------- .github/workflows/tests.yml | 10 +++-- 3 files changed, 41 insertions(+), 36 deletions(-) diff --git a/.github/workflows/component-integration.yml b/.github/workflows/component-integration.yml index 020ac8d5..7f3c1a44 100644 --- a/.github/workflows/component-integration.yml +++ b/.github/workflows/component-integration.yml @@ -86,9 +86,9 @@ jobs: MATRIX_COMPONENT: ${{ matrix.component }} run: | if [[ -z "$INPUT_COMPONENTS" ]] || [[ "$INPUT_COMPONENTS" == *"$MATRIX_COMPONENT"* ]]; then - echo "test=true" >> $GITHUB_OUTPUT + echo "test=true" >> "$GITHUB_OUTPUT" else - echo "test=false" >> $GITHUB_OUTPUT + echo "test=false" >> "$GITHUB_OUTPUT" fi - name: Install uv @@ -131,7 +131,7 @@ jobs: if: steps.should_test.outputs.test != 'false' run: | uv sync --all-extras --frozen - uv pip install --isolated 'pytest-html>=4.1.1' 'pytest-cov>=6.0.0' + uv pip install 'pytest-html>=4.1.1' 'pytest-cov>=6.0.0' working-directory: ${{ matrix.component }} - name: Run program-model libpng integration test @@ -168,13 +168,16 @@ jobs: - name: Generate integration test summary if: always() && steps.should_test.outputs.test != 'false' + env: + COMPONENT: ${{ matrix.component }} run: | - echo "### Integration Test Results: ${{ matrix.component }}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - if [ -f ${{ matrix.component }}/integration-test-results.xml ]; then + echo "### Integration Test Results: ${COMPONENT}" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + if [ -f "${COMPONENT}/integration-test-results.xml" ]; then python -c " import xml.etree.ElementTree as ET - tree = ET.parse('${{ matrix.component }}/integration-test-results.xml') + import os + tree = ET.parse(os.environ['COMPONENT'] + '/integration-test-results.xml') root = tree.getroot() tests = root.get('tests', '0') failures = root.get('failures', '0') @@ -187,9 +190,9 @@ jobs: print(f'- **Errors**: {errors}') print(f'- **Skipped**: {skipped}') print(f'- **Duration**: {float(time):.2f}s') - " >> $GITHUB_STEP_SUMMARY + " >> "$GITHUB_STEP_SUMMARY" else - echo "No integration test results found" >> $GITHUB_STEP_SUMMARY + echo "No integration test results found" >> "$GITHUB_STEP_SUMMARY" fi - name: Upload integration test results diff --git a/.github/workflows/system-integration.yml b/.github/workflows/system-integration.yml index 36506d9e..724668de 100644 --- a/.github/workflows/system-integration.yml +++ b/.github/workflows/system-integration.yml @@ -77,14 +77,14 @@ jobs: if: github.event_name == 'pull_request' run: | pull_number=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH") - export BUTTERCUP_NAMESPACE=pr-${pull_number}-${{ github.run_number }} - echo "BUTTERCUP_NAMESPACE=${BUTTERCUP_NAMESPACE}" >> $GITHUB_ENV + export BUTTERCUP_NAMESPACE="pr-${pull_number}-${{ github.run_number }}" + echo "BUTTERCUP_NAMESPACE=${BUTTERCUP_NAMESPACE}" >> "$GITHUB_ENV" - name: Set BUTTERCUP_NAMESPACE for branch if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' run: | - export BUTTERCUP_NAMESPACE=${GITHUB_REF_NAME/\//-}-${{ github.run_number }} - echo "BUTTERCUP_NAMESPACE=${BUTTERCUP_NAMESPACE}" >> $GITHUB_ENV + export BUTTERCUP_NAMESPACE="${GITHUB_REF_NAME/\//-}-${{ github.run_number }}" + echo "BUTTERCUP_NAMESPACE=${BUTTERCUP_NAMESPACE}" >> "$GITHUB_ENV" - name: Configure env file for minikube env: @@ -130,7 +130,7 @@ jobs: - name: Submit custom task to the CRS if: github.event_name == 'workflow_dispatch' run: | - kubectl port-forward -n $BUTTERCUP_NAMESPACE service/buttercup-ui 31323:1323 & + kubectl port-forward -n "$BUTTERCUP_NAMESPACE" service/buttercup-ui 31323:1323 & sleep 5 ./orchestrator/scripts/custom_task_crs.sh "$DATA" sleep 5 @@ -140,7 +140,7 @@ jobs: - name: Submit example-libpng task to the CRS if: github.event_name != 'workflow_dispatch' run: | - kubectl port-forward -n $BUTTERCUP_NAMESPACE service/buttercup-ui 31323:1323 & + kubectl port-forward -n "$BUTTERCUP_NAMESPACE" service/buttercup-ui 31323:1323 & sleep 5 ./orchestrator/scripts/task_crs.sh sleep 5 @@ -148,51 +148,51 @@ jobs: - name: Wait for fuzzer build processing timeout-minutes: ${{ fromJSON(env.FUZZER_BUILD_TIMEOUT) }} run: | - while ! kubectl logs -n $BUTTERCUP_NAMESPACE -l app=scheduler --tail=-1 | grep "buttercup.orchestrator.scheduler.scheduler - INFO - .* Processing build output for type FUZZER"; do + while ! kubectl logs -n "$BUTTERCUP_NAMESPACE" -l app=scheduler --tail=-1 | grep "buttercup.orchestrator.scheduler.scheduler - INFO - .* Processing build output for type FUZZER"; do sleep 60 done - name: Wait for vuln to be found timeout-minutes: ${{ fromJSON(env.VULN_TIMEOUT) }} run: | - while ! kubectl logs -n $BUTTERCUP_NAMESPACE -l app=scheduler --tail=-1 | grep "] POV submission response: pov_id=" ; do + while ! kubectl logs -n "$BUTTERCUP_NAMESPACE" -l app=scheduler --tail=-1 | grep "] POV submission response: pov_id=" ; do sleep 60 done - name: Wait for vuln to enter the passed state in competition api timeout-minutes: ${{ fromJSON(env.VULN_TIMEOUT) }} run: | - while ! kubectl logs -n $BUTTERCUP_NAMESPACE -l app=scheduler --tail=-1 | grep "Updated POV status. New status PASSED" ; do + while ! kubectl logs -n "$BUTTERCUP_NAMESPACE" -l app=scheduler --tail=-1 | grep "Updated POV status. New status PASSED" ; do sleep 60 done - name: Wait for seed-gen to submit at least 1 seed timeout-minutes: ${{ fromJSON(env.SEED_GEN_TIMEOUT) }} run: | - while ! kubectl logs -n $BUTTERCUP_NAMESPACE -l app=seed-gen -c seed-gen --tail=-1 | grep "Copied [1-9][0-9]* files to corpus"; do + while ! kubectl logs -n "$BUTTERCUP_NAMESPACE" -l app=seed-gen -c seed-gen --tail=-1 | grep "Copied [1-9][0-9]* files to corpus"; do sleep 60 done - name: Wait for merger-bot to submit files to remote corpus and prune local corpus timeout-minutes: ${{ fromJSON(env.MERGER_TIMEOUT) }} run: | - while ! kubectl logs -n $BUTTERCUP_NAMESPACE -l app=merger-bot -c merger-bot --tail=-1 | grep "Synced [1-9][0-9]* files that add coverage to remote corpus"; do + while ! kubectl logs -n "$BUTTERCUP_NAMESPACE" -l app=merger-bot -c merger-bot --tail=-1 | grep "Synced [1-9][0-9]* files that add coverage to remote corpus"; do sleep 60 done - while ! kubectl logs -n $BUTTERCUP_NAMESPACE -l app=merger-bot -c merger-bot --tail=-1 | grep "Removed [1-9][0-9]* files from local corpus .* that don't add coverage"; do + while ! kubectl logs -n "$BUTTERCUP_NAMESPACE" -l app=merger-bot -c merger-bot --tail=-1 | grep "Removed [1-9][0-9]* files from local corpus .* that don't add coverage"; do sleep 60 done - name: Wait for patch to be recorded timeout-minutes: ${{ fromJSON(env.PATCH_TIMEOUT) }} run: | - while ! kubectl logs -n $BUTTERCUP_NAMESPACE -l app=scheduler --tail=-1 | grep "Appending patch for task"; do + while ! kubectl logs -n "$BUTTERCUP_NAMESPACE" -l app=scheduler --tail=-1 | grep "Appending patch for task"; do sleep 60 done - name: Approve the patch in the buttercup-ui run: | # Wait until a log line with competition_patch_id= is available, then extract PATCH_ID and TASK_ID from that line while true; do - PATCH_LINE=$(kubectl logs -n $BUTTERCUP_NAMESPACE -l app=scheduler --tail=-1 | grep "competition_patch_id=" | head -n 1) + PATCH_LINE=$(kubectl logs -n "$BUTTERCUP_NAMESPACE" -l app=scheduler --tail=-1 | grep "competition_patch_id=" | head -n 1) if [ -n "$PATCH_LINE" ]; then break fi @@ -206,41 +206,41 @@ jobs: TASK_ID=$(echo "$PATCH_LINE" | sed -n 's/.*\[\([^]]*\)\].*/\1/p' | sed 's/^[^:]*://') echo "Patch ID: $PATCH_ID" echo "Task ID: $TASK_ID" - kubectl port-forward -n $BUTTERCUP_NAMESPACE service/buttercup-ui 31323:1323 & + kubectl port-forward -n "$BUTTERCUP_NAMESPACE" service/buttercup-ui 31323:1323 & sleep 5 - curl -X POST http://localhost:31323/v1/task/${TASK_ID}/patch/${PATCH_ID}/approve + curl -X POST "http://localhost:31323/v1/task/${TASK_ID}/patch/${PATCH_ID}/approve" sleep 5 - name: Wait for patch to enter the passed state in competition api timeout-minutes: ${{ fromJSON(env.PATCH_TIMEOUT) }} run: | - while ! kubectl logs -n $BUTTERCUP_NAMESPACE -l app=scheduler --tail=-1 | grep "Patch passed"; do + while ! kubectl logs -n "$BUTTERCUP_NAMESPACE" -l app=scheduler --tail=-1 | grep "Patch passed"; do sleep 60 done - name: Wait for bundle to be submitted timeout-minutes: ${{ fromJSON(env.BUNDLE_TIMEOUT) }} run: | - while ! kubectl logs -n $BUTTERCUP_NAMESPACE -l app=scheduler --tail=-1 | grep "Bundle submission response: bundle_id="; do + while ! kubectl logs -n "$BUTTERCUP_NAMESPACE" -l app=scheduler --tail=-1 | grep "Bundle submission response: bundle_id="; do sleep 60 done - name: Send a SARIF broadcast timeout-minutes: ${{ fromJSON(env.SARIF_TIMEOUT) }} run: | - TASK_ID=$(kubectl logs -n $BUTTERCUP_NAMESPACE -l app=scheduler --tail=-1 | grep "Submitting bundle for harness" | grep -o "\[[^]]*\]" | grep -o "[^[]*$" | cut -d: -f3 | tr -d ']') + TASK_ID=$(kubectl logs -n "$BUTTERCUP_NAMESPACE" -l app=scheduler --tail=-1 | grep "Submitting bundle for harness" | grep -o "\[[^]]*\]" | grep -o "[^[]*$" | cut -d: -f3 | tr -d ']') echo "Task ID: $TASK_ID" while true; do # Kill any existing port-forward processes pkill -f "kubectl port-forward.*buttercup-competition-api" || true # Start port forwarding in background - kubectl port-forward -n $BUTTERCUP_NAMESPACE service/buttercup-ui 31323:1323 & + kubectl port-forward -n "$BUTTERCUP_NAMESPACE" service/buttercup-ui 31323:1323 & # Give port-forward time to establish sleep 5 # Try to send SARIF report - if ./orchestrator/scripts/send_sarif.sh $TASK_ID; then + if ./orchestrator/scripts/send_sarif.sh "$TASK_ID"; then # Success - exit loop break fi @@ -251,13 +251,13 @@ jobs: - name: Wait for Bundle to be patched to include the SARIF timeout-minutes: ${{ fromJSON(env.SARIF_TIMEOUT) }} run: | - while ! kubectl logs -n $BUTTERCUP_NAMESPACE -l app=scheduler --tail=-1 | grep "Bundle patch submission response: broadcast_sarif_id="; do + while ! kubectl logs -n "$BUTTERCUP_NAMESPACE" -l app=scheduler --tail=-1 | grep "Bundle patch submission response: broadcast_sarif_id="; do sleep 60 done - name: Wait for SARIF to be submitted as correct timeout-minutes: ${{ fromJSON(env.SARIF_TIMEOUT) }} run: | - while ! kubectl logs -n $BUTTERCUP_NAMESPACE -l app=scheduler --tail=-1 | grep "Matching SARIF submission response"; do + while ! kubectl logs -n "$BUTTERCUP_NAMESPACE" -l app=scheduler --tail=-1 | grep "Matching SARIF submission response"; do sleep 60 done diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8b7b36f0..cd267614 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -112,9 +112,9 @@ jobs: - name: Setup ${{ matrix.component }} component run: | uv sync --all-extras --frozen - # Install test reporting tools in isolated environment + # Install test reporting tools into the project venv # This avoids adding them to every component's dependencies - uv pip install --isolated 'pytest-html>=4.1.1' 'pytest-cov>=6.0.0' + uv pip install 'pytest-html>=4.1.1' 'pytest-cov>=6.0.0' working-directory: ${{ matrix.component }} - name: Run tests on ${{ matrix.component }} component @@ -134,11 +134,13 @@ jobs: - name: Audit dependencies for vulnerabilities if: always() run: | - uv pip install --isolated pip-audit # Ignore CVEs with no available fix: # - CVE-2025-67221: orjson DoS via deeply nested JSON (no fix available) # - CVE-2026-0994: protobuf DoS via json_format.ParseDict (no fix available) - uv run pip-audit --strict --desc \ + # Use --skip-editable to ignore local packages not on PyPI + # Use uvx to run pip-audit in an isolated environment + uvx pip-audit --strict --desc \ + --skip-editable \ --ignore-vuln CVE-2025-67221 \ --ignore-vuln CVE-2026-0994 working-directory: ${{ matrix.component }}