Skip to content

mjkoo/vitiate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

138 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Vitiate

Coverage-guided fuzzing for JavaScript and TypeScript, built as a Vitest plugin.

Vitiate uses SWC for compile-time instrumentation and LibAFL for mutation-driven fuzzing. Write fuzz tests alongside your unit tests in Vitest, or use the standalone CLI for libFuzzer-compatible workflows.

Documentation

Quickstart

Install the package:

npm install --save-dev vitiate

This 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.

Configure Vitest (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"] } },
    ],
  },
});

Write a fuzz test (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
    }
  }
});

Run the fuzzer:

npx vitiate fuzz

Or via Vitest directly:

VITIATE_FUZZ=1 npx vitest run

Crashes are saved to .vitiate/testdata/<hashdir>/crashes/crash-<sha256> (where <hashdir> is a base32 encoded name like vxr4kpqyb12fza1gv81bjj8k3i64mlqn-parse_does_not_crash). Run your test suite normally and they are replayed as regression tests automatically.

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/

Dependency Fuzzing

To fuzz code inside third-party npm packages, use the instrument.packages option. The plugin automatically handles module inlining and transform configuration for listed packages.

How It Works

  1. Transform time: Vite's plugin hooks run every JS/TS module through the SWC plugin as it is imported, inserting edge coverage counters and comparison tracing calls. No separate build step required.
  2. Runtime: A shared coverage map (zero-copy between JS and Rust) tracks which edges are hit.
  3. Fuzz loop: LibAFL reads the coverage map after each execution, evaluates feedback, updates the corpus, and generates the next input using havoc mutations, CmpLog-guided byte replacement, Grimoire structure-aware mutations, and Unicode-aware mutations.
  4. Crashes: Inputs that cause uncaught exceptions are saved as crash artifacts under .vitiate/testdata/ and minimized automatically.

Platform Support

Prebuilt native binaries are provided for:

  • Linux: x86_64 (glibc, musl), aarch64 (glibc, musl), armv7 (gnueabihf)
  • macOS: aarch64 (Apple Silicon)
  • Windows: x86_64

Requires Node.js 18 or later, Vite 6+, and Vitest 3.1+.

Packages

Package Description
vitiate Wrapper package with the vitiate CLI binary
@vitiate/core Vite plugin, fuzz API, corpus management
@vitiate/engine Native Node.js addon wrapping LibAFL (prebuilt binaries)
@vitiate/swc-plugin SWC WASM plugin for coverage instrumentation
@vitiate/fuzzed-data-provider Structured fuzzing helper (optional)