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 <noreply@anthropic.com>
This commit is contained in:
Riccardo Schirone
2026-01-26 15:12:49 +01:00
committed by Riccardo Schirone
parent 47f38b8863
commit 834cbf3cab
3 changed files with 41 additions and 36 deletions
+12 -9
View File
@@ -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
+23 -23
View File
@@ -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
+6 -4
View File
@@ -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 }}