name: Fuzz
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
concurrency:
group: fuzz-${{ github.ref }}
cancel-in-progress: true
jobs:
fuzz:
name: Fuzz V8 Unsafe Code
runs-on: [self-hosted, railway]
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
target:
- fuzz_snapshot_deserialization
- fuzz_execute_stateless
- fuzz_execute_stateful
- fuzz_wasm_compile
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v30
with:
extra_nix_config: |
experimental-features = nix-command flakes
- name: Install cargo-fuzz
run: nix develop .#fuzz --command bash -c "cargo install cargo-fuzz"
- name: Build fuzz targets
run: nix develop .#fuzz --command bash -c "cd server && cargo fuzz build --sanitizer=address ${{ matrix.target }}"
- name: Run fuzzer (${{ matrix.target }})
env:
ASAN_OPTIONS: quarantine_size_mb=64:malloc_context_size=5:allocator_may_return_null=1
run: |
# Use fork mode (-fork=1) so each worker gets a clean address space,
# preventing cumulative ASAN shadow memory growth from killing the
# parent process.
#
# RSS limit is set high (8GB) to accommodate ASAN shadow memory
# overhead (~8x real memory). V8 isolate creation + WASM compilation
# paths use significant native memory that, with ASAN instrumentation,
# can exceed tighter limits. Genuine resource-exhaustion bugs are
# caught by wasmparser resource validation (memory page / table size
# limits) before reaching V8.
nix develop .#fuzz --command bash -c \
"cd server && cargo fuzz run --sanitizer=address ${{ matrix.target }} \
-- -fork=1 -max_total_time=300 -rss_limit_mb=8192 -max_len=65536 -timeout=120"
- name: Upload fuzz artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: fuzz-artifacts-${{ matrix.target }}
path: server/fuzz/artifacts/
if-no-files-found: ignore
- name: Upload corpus
if: always()
uses: actions/upload-artifact@v4
with:
name: fuzz-corpus-${{ matrix.target }}
path: server/fuzz/corpus/${{ matrix.target }}/
if-no-files-found: ignore