Quickstart
1. Install
Section titled “1. Install”npm install --save-dev vitiateThis installs the Vitest plugin, the standalone CLI, and all required dependencies.
If you only need the Vitest plugin and not the CLI, you can install @vitiate/core instead.
2. Configure Vitest
Section titled “2. Configure Vitest”Create or update your vitest.config.ts:
import { defineConfig } from "vitest/config";import { vitiatePlugin } from "@vitiate/core/plugin";
export default defineConfig({ plugins: [vitiatePlugin()], test: { projects: [ { extends: true, test: { name: "unit", include: ["test/**/*.test.ts"] } }, { extends: true, test: { name: "fuzz", include: ["test/**/*.fuzz.ts"] } }, ], },});The vitiatePlugin() call registers the SWC instrumentation plugin with Vite and automatically configures the setup file that initializes coverage map and comparison tracing globals. The two test projects keep your unit tests and fuzz tests separate - Vitest runs them in different contexts.
3. Write a Fuzz Test
Section titled “3. Write a Fuzz Test”Create test/parser.fuzz.ts:
import { fuzz } from "@vitiate/core";import { parse, ParseError } from "../src/parser.js";
fuzz("parse does not crash", (data: Buffer) => { try { parse(data.toString("utf-8")); } catch (error) { if (!(error instanceof ParseError)) { throw error; // re-throw unexpected errors } }});The fuzz() function works like Vitest’s test(). It receives a name, a target function that takes a Buffer, and optional configuration. The fuzzer will call your target with thousands of generated inputs, looking for uncaught exceptions.
Catch expected errors (like ParseError above) and let unexpected ones propagate - those are the bugs you want to find.
4. Run the Fuzzer
Section titled “4. Run the Fuzzer”Use the vitiate fuzz command to activate fuzzing mode:
npx vitiate fuzzOr set VITIATE_FUZZ=1 directly:
VITIATE_FUZZ=1 npx vitest runYou will see a startup banner followed by periodic status updates showing execution count, corpus size, and coverage edges discovered:
fuzz: elapsed: 3s, execs: 1024 (3412/sec), corpus: 23 (5 new), edges: 142fuzz: elapsed: 6s, execs: 2048 (3506/sec), corpus: 31 (8 new), edges: 158When the fuzzer finds a crash, it prints the error, minimizes the input, and saves it:
fuzz: CRASH FOUND: TypeError: Cannot read properties of undefined (reading 'length')fuzz: crash artifact written to: .vitiate/testdata/vxr4kpqyb12fza1gv81bjj8k3i64mlqn-parse_does_not_crash/crashes/crash-e5f6...Press Ctrl+C to stop fuzzing at any time. Add the following to your .gitignore:
# Vitiate cached corpus (regenerated by the fuzzer).vitiate/corpus/
# SWC WASM plugin compilation cache (created by Vitiate's instrumentation).swc/The .vitiate/corpus/ directory can grow large and is regenerated automatically. The .swc/ directory contains platform-specific compiled WASM plugin artifacts that SWC creates during instrumentation - it is safe to delete and will be recreated on the next run.
5. Replay as Regression Tests
Section titled “5. Replay as Regression Tests”Run your test suite normally (without VITIATE_FUZZ):
npx vitest runVitiate automatically replays saved crash artifacts and corpus entries in regression mode. The crash you just found is now a permanent regression test - no extra code needed.
Next Steps
Section titled “Next Steps”- Read the Fuzzing Primer if you are new to coverage-guided fuzzing
- Follow the Tutorial for a longer guided walkthrough
- Learn about Structure-Aware Fuzzing for targets that need typed inputs
- Enable Vulnerability Detectors to find security issues beyond crashes