Skip to content

Enhance README with structured content and quick start guide (#18) #71

Enhance README with structured content and quick start guide (#18)

Enhance README with structured content and quick start guide (#18) #71

Workflow file for this run

name: Continuous Fuzzing
on:
# Run on main branch pushes
push:
branches: [main]
# Run nightly
schedule:
- cron: '0 2 * * *' # 2 AM UTC daily
# Allow manual trigger
workflow_dispatch:
inputs:
duration:
description: 'Fuzz duration per target (seconds)'
required: false
default: '3600'
env:
CARGO_TERM_COLOR: always
permissions:
contents: read
issues: write # To create issues on fuzzing failures
jobs:
# Discover all fuzz targets dynamically
discover-targets:
name: Discover Fuzz Targets
runs-on: ubuntu-latest
outputs:
targets: ${{ steps.list-targets.outputs.targets }}
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: nightly
- name: Install cargo-fuzz
run: cargo install cargo-fuzz
- name: List fuzz targets
id: list-targets
run: |
cd fuzz
# Get all fuzz targets and format as JSON array
TARGETS=$(cargo fuzz list | jq -R -s -c 'split("\n") | map(select(length > 0))')
echo "targets=$TARGETS" >> $GITHUB_OUTPUT
echo "Found targets:"
echo "$TARGETS" | jq -r '.[]'
# Long-running fuzz tests
fuzz-continuous:
name: Fuzz ${{ matrix.target }}
runs-on: ubuntu-latest
needs: discover-targets
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
target: ${{ fromJson(needs.discover-targets.outputs.targets) }}
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: nightly
- uses: Swatinem/rust-cache@v2
with:
workspaces: "fuzz -> target"
- name: Install cargo-fuzz
run: cargo install cargo-fuzz
- name: Restore corpus
uses: actions/cache@v5
with:
path: fuzz/corpus/${{ matrix.target }}
key: fuzz-corpus-${{ matrix.target }}-${{ github.sha }}
restore-keys: |
fuzz-corpus-${{ matrix.target }}-
- name: Run fuzz target
id: fuzz
continue-on-error: true
run: |
cd fuzz
DURATION=${{ github.event.inputs.duration || '3600' }}
echo "Running ${{ matrix.target }} for ${DURATION} seconds"
cargo fuzz run ${{ matrix.target }} -- \
-max_total_time=${DURATION} \
-print_final_stats=1 \
-print_corpus_stats=1 \
-rss_limit_mb=4096
env:
RUST_BACKTRACE: full
- name: Minimize corpus
if: always()
run: |
cd fuzz
cargo fuzz cmin ${{ matrix.target }}
- name: Save corpus
if: always()
uses: actions/cache/save@v5
with:
path: fuzz/corpus/${{ matrix.target }}
key: fuzz-corpus-${{ matrix.target }}-${{ github.sha }}
- name: Check for crashes
id: check_crashes
if: always()
run: |
if [ -d "fuzz/artifacts/${{ matrix.target }}" ] && [ "$(ls -A fuzz/artifacts/${{ matrix.target }})" ]; then
echo "crashes_found=true" >> $GITHUB_OUTPUT
echo "## Crashes found in ${{ matrix.target }}" >> $GITHUB_STEP_SUMMARY
ls -lh fuzz/artifacts/${{ matrix.target }}/ >> $GITHUB_STEP_SUMMARY
else
echo "crashes_found=false" >> $GITHUB_OUTPUT
echo "✅ No crashes found in ${{ matrix.target }}" >> $GITHUB_STEP_SUMMARY
fi
- name: Upload crash artifacts
if: steps.check_crashes.outputs.crashes_found == 'true'
uses: actions/upload-artifact@v6
with:
name: fuzz-crashes-${{ matrix.target }}-${{ github.sha }}
path: fuzz/artifacts/${{ matrix.target }}/
retention-days: 90
- name: Create issue for crashes
if: steps.check_crashes.outputs.crashes_found == 'true' && github.event_name == 'schedule'
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
const target = '${{ matrix.target }}';
const artifactsDir = `fuzz/artifacts/${target}`;
const files = fs.readdirSync(artifactsDir);
const body = `
## 🐛 Fuzzing Found Crashes in \`${target}\`
The continuous fuzzing workflow found ${files.length} crash(es) in the \`${target}\` fuzz target.
### Crash Files
${files.map(f => `- \`${f}\``).join('\n')}
### How to Reproduce
\`\`\`bash
cd fuzz
cargo fuzz run ${target} fuzz/artifacts/${target}/${files[0]}
\`\`\`
### Artifacts
Download crash artifacts from: [Actions Run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
### Next Steps
1. Reproduce locally using the command above
2. Fix the underlying issue
3. Add a regression test
4. Re-run fuzzing to verify the fix
---
*Automatically created by continuous fuzzing workflow*
`;
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `[FUZZ] Crashes found in ${target}`,
body: body,
labels: ['bug', 'fuzzing', 'security']
});
- name: Fail if crashes found
if: steps.check_crashes.outputs.crashes_found == 'true'
run: |
echo "::error::Fuzzing found crashes in ${{ matrix.target }}"
exit 1
# Corpus statistics and coverage
fuzz-coverage:
name: Fuzz Coverage Report
runs-on: ubuntu-latest
needs: fuzz-continuous
if: always() && github.event_name == 'schedule'
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: nightly
- uses: Swatinem/rust-cache@v2
with:
workspaces: "fuzz -> target"
- name: Install cargo-fuzz
run: cargo install cargo-fuzz
- name: Restore all corpora
uses: actions/cache@v5
with:
path: fuzz/corpus/
key: fuzz-corpus-all-${{ github.sha }}
restore-keys: |
fuzz-corpus-
- name: Generate coverage report
run: |
cd fuzz
# Sample arbitrary_ops targets for coverage (representative subset)
# This avoids running coverage for all targets which would be too slow
TARGETS=$(cargo fuzz list | grep '_arbitrary_ops$' || true)
if [ -z "$TARGETS" ]; then
echo "No arbitrary_ops targets found, using all targets" >> $GITHUB_STEP_SUMMARY
TARGETS=$(cargo fuzz list)
fi
echo "## Coverage Report" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
for target in $TARGETS; do
echo "### Coverage for $target" >> $GITHUB_STEP_SUMMARY
cargo fuzz coverage $target || true
if [ -f "coverage/$target/coverage.json" ]; then
echo "\`\`\`json" >> $GITHUB_STEP_SUMMARY
cat "coverage/$target/coverage.json" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
done
- name: Upload coverage artifacts
uses: actions/upload-artifact@v6
with:
name: fuzz-coverage-${{ github.sha }}
path: fuzz/coverage/
retention-days: 30
# Summary job
fuzz-summary:
name: Fuzzing Summary
runs-on: ubuntu-latest
needs: fuzz-continuous
if: always()
steps:
- name: Check fuzzing results
run: |
if [ "${{ needs.fuzz-continuous.result }}" != "success" ]; then
echo "⚠️ Some fuzz targets found issues or failed" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "✅ All fuzz targets passed" >> $GITHUB_STEP_SUMMARY
fi