mirror of
https://github.com/trailofbits/buttercup
synced 2026-06-21 14:11:39 +00:00
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:
committed by
Riccardo Schirone
parent
47f38b8863
commit
834cbf3cab
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
Reference in New Issue
Block a user