nsip 0.4.0

NSIP Search API client for nsipsearch.nsip.org/api
Documentation
name: Fuzz Testing

on:
  # schedule:
  #   - cron: '0 2 * * *'  # Daily at 2 AM
  workflow_dispatch:
    inputs:
      duration:
        description: 'Fuzz duration in seconds'
        required: false
        default: '300'
      target:
        description: 'Specific fuzz target to run'
        required: false
        default: ''

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read
  issues: write

jobs:
  fuzz:
    name: Fuzz Testing with cargo-fuzz
    runs-on: ubuntu-latest
    timeout-minutes: 120
    
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2

      - name: Setup Rust with caching
        uses: ./.github/actions/setup-rust-cached
        with:
          toolchain: nightly
          cache-key: fuzz

      - name: Install cargo-fuzz
        uses: ./.github/actions/install-cargo-tool
        with:
          tool: cargo-fuzz@0.12.0

      - name: Cache fuzz corpus
        uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
        with:
          path: fuzz/corpus
          key: ${{ runner.os }}-fuzz-corpus-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-fuzz-corpus-

      - name: Initialize fuzz directory if needed
        run: |
          if [ ! -d "fuzz" ]; then
            cargo fuzz init
            echo "Fuzz directory initialized. Add fuzz targets to fuzz/fuzz_targets/"
          fi

      - name: List fuzz targets
        id: targets
        run: |
          if [ -d "fuzz/fuzz_targets" ]; then
            TARGETS=$(ls fuzz/fuzz_targets/*.rs 2>/dev/null | xargs -n1 basename | sed 's/\.rs$//' || echo "")
            echo "targets=$TARGETS" >> $GITHUB_OUTPUT
            echo "Found targets: $TARGETS"
          else
            echo "targets=" >> $GITHUB_OUTPUT
            echo "No fuzz targets found"
          fi

      - name: Run fuzz tests
        if: steps.targets.outputs.targets != ''
        run: |
          DURATION="${{ github.event.inputs.duration || '300' }}"
          TARGET="${{ github.event.inputs.target }}"
          
          mkdir -p fuzz-results
          
          if [ -n "$TARGET" ]; then
            echo "Running fuzz target: $TARGET for ${DURATION}s"
            timeout ${DURATION}s cargo fuzz run $TARGET -- -max_total_time=${DURATION} || true
          else
            for target in ${{ steps.targets.outputs.targets }}; do
              echo "Running fuzz target: $target for ${DURATION}s"
              timeout ${DURATION}s cargo fuzz run $target -- -max_total_time=${DURATION} || true
            done
          fi

      - name: Check for crashes
        if: steps.targets.outputs.targets != ''
        id: crashes
        run: |
          if [ -d "fuzz/artifacts" ] && [ "$(ls -A fuzz/artifacts 2>/dev/null)" ]; then
            echo "crashes=true" >> $GITHUB_OUTPUT
            echo "# Fuzz Testing Crashes Detected" > fuzz-report.md
            echo "" >> fuzz-report.md
            for artifact in fuzz/artifacts/*/*; do
              if [ -f "$artifact" ]; then
                echo "## Crash: $(basename $(dirname $artifact))" >> fuzz-report.md
                echo "File: $artifact" >> fuzz-report.md
                echo "\`\`\`" >> fuzz-report.md
                xxd -l 256 "$artifact" >> fuzz-report.md || cat "$artifact" >> fuzz-report.md
                echo "\`\`\`" >> fuzz-report.md
                echo "" >> fuzz-report.md
              fi
            done
          else
            echo "crashes=false" >> $GITHUB_OUTPUT
            echo "# Fuzz Testing - No Crashes" > fuzz-report.md
            echo "All fuzz targets completed successfully." >> fuzz-report.md
          fi

      - name: Upload fuzz artifacts
        if: always() && steps.targets.outputs.targets != ''
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f  # v7.0.0
        with:
          name: fuzz-results
          path: |
            fuzz-report.md
            fuzz/artifacts/
            fuzz/corpus/
          retention-days: 90

      - name: Create issue on crash
        if: steps.crashes.outputs.crashes == 'true'
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
        with:
          script: |
            const fs = require('fs');
            const report = fs.readFileSync('fuzz-report.md', 'utf8');
            
            github.rest.issues.create({
              owner: context.repo.owner,
              repo: context.repo.repo,
              title: `Fuzz Testing: Crashes detected on ${new Date().toISOString().split('T')[0]}`,
              body: report,
              labels: ['bug', 'fuzz-testing', 'security']
            });