A web-based orchestration platform for running grammar-based fuzzers, collecting results, analyzing findings, and enabling team collaboration.
Automatically parses results from web-fuzzer, stores them in a database, and provides a web UI for finding classification, triage, commenting, and collaborative workflows.
- Dashboard — View active sessions, recent findings, and overall statistics at a glance
- Project Management — Group fuzzing sessions by project with aggregated statistics
- Session Control — Start/stop web-fuzzer directly from the web UI with real-time log and coverage monitoring (WebSocket)
- Result Import — Import existing web-fuzzer results by specifying the output directory path
- Finding Analysis — Filter by severity/oracle/triage, bulk triage, deduplication (fingerprint), input preview, per-finding tag management
- Corpus Management — Browse corpus entries, preview inputs, adjust seed priority (forwarded to running fuzzer in real-time)
- Collaboration — Per-finding comments, tags, triage workflow (new → confirmed / false_positive / fixed / wont_fix)
- Target Presets — 13 pre-configured fuzzing targets (URL parsers, sanitizers, etc.) for quick session setup
- Extensibility — Designed to accommodate results from other fuzzers (AFL, LibFuzzer, etc.) via the
fuzzer_typefield
| Layer | Technology |
|---|---|
| Frontend | React 19, TypeScript, Vite 7, TailwindCSS v4, Recharts, Lucide React |
| Backend | Python FastAPI, SQLAlchemy 2.0, SQLite (WAL mode) |
| Real-time | WebSocket (FastAPI native), Redis Pub/Sub |
- Python 3.11+
- Node.js 18+
- web-fuzzer installed at
C:\Users\dmbs3\web-fuzzer(configurable via environment variables) - (Optional) Redis server — required for real-time data pipeline. Falls back to stderr parsing without it
cd fuzzer-orchestrator
# Install backend dependencies
pip install -r backend/requirements.txt
# Install frontend dependencies
cd frontend
npm install
cd ..Development mode (frontend + backend separately):
# Terminal 1: Backend (port must match vite proxy target)
uvicorn backend.main:app --reload --port 8003
# Terminal 1 (Redis real-time mode):
set FUZZER_REDIS_URL=redis://localhost:6379/0
uvicorn backend.main:app --reload --port 8003
# Terminal 2: Frontend (Vite dev server, proxies /api and /ws to backend)
cd frontend
npm run devOpen
http://localhost:5175in your browser
Production mode (single server):
# Build frontend
cd frontend && npm run build && cd ..
# Backend automatically serves frontend/dist
uvicorn backend.main:app --port 8003Open
http://localhost:8003in your browser
All environment variables use the FUZZER_ prefix (via pydantic-settings).
| Variable | Default | Description |
|---|---|---|
FUZZER_WEBFUZZER_PATH |
C:/Users/dmbs3/web-fuzzer |
Root path of the web-fuzzer project |
FUZZER_WEBFUZZER_CMD |
python -m webfuzzer |
Command to run web-fuzzer |
FUZZER_DB_URL |
sqlite:///backend/data/fuzzer.db |
Database URL |
FUZZER_REDIS_URL |
"" (disabled) |
Redis connection URL (e.g., redis://localhost:6379/0). Enables real-time Pub/Sub mode when set |
FUZZER_CORS_ORIGINS |
["http://localhost:5175", "http://localhost:5174", "http://localhost:5173", "http://localhost:3000"] |
Allowed CORS origins |
web-fuzzer publishes events (stats, findings, coverage, corpus, status) to Redis Pub/Sub in real-time during fuzzing. The Orchestrator subscribes and immediately saves them to the database + broadcasts via WebSocket.
web-fuzzer (Publisher) Redis Orchestrator (Subscriber)
======================== ========= ====================================
engine.run() ──────> fuzzer:42:status ──> DB status update + WS broadcast
_maybe_print_status() ──────> fuzzer:42:stats ──> DB stats update + WS broadcast
_check_oracles() ──────> fuzzer:42:finding ──> DB real-time insert + WS broadcast
corpus.add() success ──────> fuzzer:42:coverage ──> DB snapshot insert + WS broadcast
──────> fuzzer:42:corpus ──> DB entry insert
If FUZZER_REDIS_URL is not set, both sides fall back to the legacy stderr regex parsing mode. No frontend changes required.
# 1. Start Redis server
redis-server
# 2. Set environment variable and start Orchestrator
set FUZZER_REDIS_URL=redis://localhost:6379/0
uvicorn backend.main:app --port 8003When the Orchestrator launches a session, it automatically passes FUZZER_REDIS_URL and FUZZER_SESSION_ID to the subprocess environment.
| Channel Pattern | Data | Frequency |
|---|---|---|
fuzzer:{session_id}:stats |
{elapsed_seconds, total_execs, execs_per_sec, corpus_size, total_edges, unique_findings} |
~5s |
fuzzer:{session_id}:finding |
{title, severity, oracle_name, fingerprint, input_hex, input_preview, exit_code, duration_ms, metadata} |
On discovery |
fuzzer:{session_id}:coverage |
{edge_count, elapsed_sec, exec_count, corpus_size} |
On new coverage |
fuzzer:{session_id}:corpus |
{seed_id, size_bytes, input_hex, parent_id, depth, energy, metadata} |
On new seed |
fuzzer:{session_id}:log |
{line} |
Per log line |
fuzzer:{session_id}:status |
{status} |
started/completed/failed/stopped |
External processes (not web-fuzzer) can also publish to the channels above in the same format, and the Orchestrator will automatically collect the data.
import redis, json
r = redis.Redis.from_url("redis://localhost:6379/0", decode_responses=True)
session_id = 42
# Send a finding
r.publish(f"fuzzer:{session_id}:finding", json.dumps({
"title": "Buffer overflow in parse()",
"severity": "critical",
"oracle_name": "sanitizer",
"fingerprint": "abc123",
"input_hex": "deadbeef",
"input_preview": "...",
"exit_code": -11,
"duration_ms": 15,
"metadata": {"info": "heap-buffer-overflow"}
}))
# Notify session completion
r.publish(f"fuzzer:{session_id}:status", json.dumps({"status": "completed"}))If you already have results from running web-fuzzer via CLI:
-
Start the Fuzzer Orchestrator server
-
Click Import in the left sidebar
-
Select a Project (create one first on the Projects page if needed)
-
Enter the absolute path to the web-fuzzer output directory in Results Directory
Example: C:\Users\dmbs3\web-fuzzer\results\json_diff -
(Optional) Enter a Session Name — defaults to the directory name if left blank
-
Click Import Results
After import completes, the number of findings, corpus entries, and duplicates will be displayed. Click View Session to inspect the results immediately.
Use this for automated imports from scripts or CI/CD pipelines.
# 1. Create a project (skip if already exists)
curl -X POST http://localhost:8003/api/projects \
-H "Content-Type: application/json" \
-d '{"name": "My JSON Fuzzing", "description": "JSON parser fuzz testing"}'
# Note the project id from the response (e.g., 1)
# 2. Import results
curl -X POST http://localhost:8003/api/import/webfuzzer \
-H "Content-Type: application/json" \
-d '{
"project_id": 1,
"output_dir": "C:/Users/dmbs3/web-fuzzer/results/json_diff",
"session_name": "json_diff_run1"
}'Example response:
{
"session_id": 1,
"findings_imported": 270,
"corpus_imported": 165,
"duplicates_found": 0
}When you start web-fuzzer from the Orchestrator, results are collected automatically.
- Click New Session in the left sidebar
- Configure the following:
- Project: Select a target project
- Grammar: Select an input grammar (json, html, ecmascript, uri, csp, cookie, multipart)
- Target: Choose from 13 pre-configured target presets or enter a custom command
- Mutators: Select mutation strategies (grammar, havoc, token, splice, dictionary, mxss, structural)
- Scheduler: Seed scheduling strategy (random, entropic, ecofuzz, rare-branch)
- Oracles: Bug detection oracles (crash, response, sanitizer, xss, mxss, ssrf, url_confusion)
- Max Iterations / Max Time: Execution limits (0 = unlimited)
- Differential Reference Commands: Reference commands for differential fuzzing (one per line)
- Click Create & Start to launch the fuzzer immediately
During execution, the Session Detail page shows real-time:
- Execution statistics (exec/s, corpus size, edges, findings)
- Coverage growth chart
- Mutator efficiency chart
- Live logs
Redis mode (when FUZZER_REDIS_URL is set): Findings, corpus, and coverage are saved to the database in real-time as they are discovered. No need to wait for the session to end.
Fallback mode (without Redis): Only stats are collected in real-time via stderr parsing. Findings and corpus are bulk-imported from the filesystem after the session completes.
The import recognizes the following directory structure:
output_dir/
├── report.json # Session statistics (duration, exec count, coverage, etc.)
├── findings/
│ ├── 0000_low_crash/ # {index}_{severity}_{oracle}
│ │ ├── info.json # Title, severity, fingerprint, exit_code, metadata
│ │ └── input # The input that triggered the crash
│ ├── 0001_high_differential/
│ │ ├── info.json
│ │ └── input
│ └── ...
└── corpus/
├── id_000000 # Seed input data
├── id_000000.meta # Seed metadata (depth, energy, exec_count, etc.)
├── id_000001
├── id_000001.meta
└── ...
findings/info.json format:
{
"title": "Differential: accept/reject mismatch (ref[0])",
"severity": "high",
"oracle": "differential",
"fingerprint": "d8be2a24a5fedfc7",
"exit_code": 1,
"duration_ms": 187.0,
"metadata": {
"strategy": "exit_code",
"primary_exit": 1,
"ref_exit": 0
}
}report.json format:
{
"elapsed_seconds": 612.71,
"total_executions": 530,
"executions_per_second": 0.9,
"corpus_size": 165,
"total_edges": 171,
"peak_edges": 171,
"unique_findings": 270,
"findings_by_severity": { "low": 248, "high": 22 },
"findings_by_oracle": { "crash": 248, "differential": 22 },
"mutations_by_mutator": { "grammar": 126, "havoc": 135, "token": 114, "dictionary": 125 },
"new_coverage_by_mutator": { "grammar": 96, "token": 23, "dictionary": 15, "havoc": 1 }
}Currently only the web-fuzzer format is supported, but extension points are ready:
- Add a new parser class in
backend/services/result_parser.py - Add a new endpoint in
backend/routers/import_.py(e.g.,POST /api/import/afl) - Differentiate fuzzer types using the
FuzzSession.fuzzer_typefield
Planned support: AFL/AFL++, LibFuzzer, Honggfuzz
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/projects |
List projects (with statistics) |
| POST | /api/projects |
Create a project |
| GET | /api/projects/{id} |
Project details |
| PUT | /api/projects/{id} |
Update a project |
| DELETE | /api/projects/{id} |
Delete a project |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/sessions |
List sessions |
| POST | /api/sessions |
Create a session |
| GET | /api/sessions/{id} |
Session details |
| PUT | /api/sessions/{id} |
Update a session |
| DELETE | /api/sessions/{id} |
Delete a session |
| POST | /api/sessions/{id}/start |
Start fuzzer execution |
| POST | /api/sessions/{id}/stop |
Stop fuzzer execution |
| GET | /api/sessions/{id}/stats |
Session detailed statistics |
| GET | /api/sessions/{id}/coverage |
Coverage timeline |
| POST | /api/sessions/{id}/command |
Send command to running fuzzer (e.g., set_priority) |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/findings |
List findings (filter/pagination) |
| GET | /api/findings/stats |
Finding statistics |
| PUT | /api/findings/bulk |
Bulk triage |
| GET | /api/findings/{id} |
Finding details |
| PUT | /api/findings/{id} |
Update finding triage |
| DELETE | /api/findings/{id} |
Delete a finding |
| GET | /api/findings/{id}/input |
Download finding input data |
| POST | /api/findings/{id}/tags |
Add tag to finding |
| DELETE | /api/findings/{id}/tags/{tag_id} |
Remove tag from finding |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/corpus |
List corpus entries |
| GET | /api/corpus/{id} |
Corpus entry details |
| GET | /api/corpus/{id}/preview |
Text preview of corpus input |
| GET | /api/corpus/{id}/input |
Download corpus input data |
| DELETE | /api/corpus/{id} |
Delete a corpus entry |
| PATCH | /api/corpus/{id}/priority |
Update seed priority (forwarded to running fuzzer) |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/tags |
List tags |
| POST | /api/tags |
Create a tag |
| PUT | /api/tags/{id} |
Update a tag |
| DELETE | /api/tags/{id} |
Delete a tag |
| GET | /api/comments |
List comments |
| POST | /api/comments |
Create a comment |
| PUT | /api/comments/{id} |
Update a comment |
| DELETE | /api/comments/{id} |
Delete a comment |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/import/webfuzzer |
Import web-fuzzer results |
| GET | /api/import/formats |
List supported import formats |
| GET | /api/config |
List all available grammars/mutators/schedulers/oracles |
| GET | /api/config/grammars |
List available grammars |
| GET | /api/config/mutators |
List available mutators |
| GET | /api/config/schedulers |
List available schedulers |
| GET | /api/config/oracles |
List available oracles |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/health |
Health check |
| WS | /ws/{session_id} |
Real-time updates per session |
| WS | /ws/global |
Global event stream |
fuzzer-orchestrator/
├── backend/
│ ├── main.py # FastAPI app entry point
│ ├── config.py # Configuration (DB URL, web-fuzzer path, etc.)
│ ├── database.py # SQLAlchemy engine + session
│ ├── models.py # ORM models (9 tables)
│ ├── schemas.py # Pydantic request/response schemas
│ ├── routers/ # API routers
│ │ ├── projects.py # Project CRUD
│ │ ├── sessions.py # Session CRUD + execution control + commands
│ │ ├── findings.py # Finding filter/triage/tags
│ │ ├── corpus.py # Corpus queries + priority management
│ │ ├── tags.py # Tag CRUD
│ │ ├── comments.py # Comment CRUD
│ │ ├── import_.py # Result import
│ │ ├── config_.py # Fuzzer config + target presets
│ │ └── ws.py # WebSocket
│ ├── services/
│ │ ├── fuzzer_service.py # Run/stop web-fuzzer as subprocess (auto Redis/stderr fallback)
│ │ ├── redis_subscriber.py # Redis Pub/Sub subscriber + real-time DB writes
│ │ ├── result_parser.py # Filesystem result parser
│ │ └── websocket_manager.py # WebSocket connection manager
│ └── requirements.txt
└── frontend/
├── src/
│ ├── pages/ # 9 pages
│ ├── components/ # Reusable UI components
│ ├── api/ # Axios API client
│ ├── hooks/ # useWebSocket, etc.
│ ├── types/ # TypeScript interfaces
│ └── utils/ # Formatters, color mappings
└── package.json