Fuzzing Mode
Fuzzing mode (RUN_MODE=fuzzing) points STAR at a different class of bug than the rest of the agent. The DAST run modes answer "can an attacker reach a known sink with a known payload?" — SQLi, XSS, SSRF, BOLA, and the rest of the Vulnerabilities Index. Fuzzing mode answers a question no payload list can: "what happens when code that already passed input validation meets input nobody expected?" It evolves malformed, structure-aware inputs into your input-handling code and watches for the application to break — crash, hang, run out of memory, or trip a native memory-safety guard.
If a bug class has a payload list and a signature detector, that is DAST's job, not fuzzing's. Fuzzing mode deliberately ships no SQLi/XSS/SSRF oracle. Its value is the availability and process-integrity bugs that live past input validation, where no one wrote a signature.
At a glanceFuzzing mode is a separate, self-contained run mode. It does not start your application, open a Bright Repeater, run a cloud DAST scan, detect auth, or run the scan → fix → validate loop described in Intro. It resolves a standalone fuzzer binary, has an LLM write per-function harnesses, fuzzes them in Docker, triages the faults, and (optionally) fixes and re-validates them — all locally on your runner, aside from a startup license check against your Bright token (
BRIGHT_TOKEN), the same credential the rest of STAR uses.
When to use it
Reach for fuzzing mode when your code parses, decodes, or transforms untrusted bytes and a malformed input could do more than return an error — file-format parsers, serializers, protocol decoders, compression/encoding routines, template or expression evaluators, native/FFI bindings. These are exactly the paths where a cleverly malformed input doesn't trigger a signatured vulnerability — it just breaks the program, and exactly the paths a signature-based DAST scan tends to pass straight through.
It is strongest on:
- Native and FFI-adjacent code (C/C++ called through bindings, Rust
unsafe, Crystal pointer work, raw pointer work) where memory-safety bugs are both most likely and most severe. - Parsers and decoders reachable from an HTTP route, CLI, file reader, queue consumer, or public library API — JSON/XML/protobuf/GraphQL readers, binary format decoders (image, archive, media), and the decompressors and codecs where a tiny input can expand into a large allocation.
- Services written in memory-safe languages (Go, Node, Rust, Crystal, .NET) where an unexpected input can still take the whole process down with an unrecoverable fatal fault or a resource bomb.
Run it alongside your normal STAR pipeline, not instead of it. A typical setup keeps RUN_MODE=full on pull requests for DAST coverage and schedules a separate RUN_MODE=fuzzing job for robustness coverage of the same repository — a second, orthogonal pass that hardens the input-handling code your endpoints call into.
How it differs from the DAST modes
STAR's full, dynamic, function, and validation modes all build on the same spine: start (or harness) the app, bridge it to the Bright Dynamic Security Scanner through a Repeater, and run the scan → fix → validate loop. Fuzzing mode shares none of that spine.
DAST modes (full / dynamic / function / validation) | Fuzzing mode (fuzzing) | |
|---|---|---|
| Starts your app | Yes (or an HTTP function harness) | No |
| Uses Bright Cloud / Repeater | Yes — the dynamic scanner | No — fully local |
| Detects & uses auth | Yes (auto-detected) | No |
| Finds | OWASP payload classes (SQLi, XSS, SSRF, BOLA, …) | Crashes, hangs, memory-safety, resource DoS |
| Finding location | HTTP endpoint + request | file:line + exact input bytes |
| Oracle | Payload signatures against responses | Process behavior (crash / timeout / memory / sanitizer) |
See Run Modes & Scan Scope for the full RUN_MODE reference and Capabilities & Use Cases for how the DAST modes map to pipeline triggers.
Fuzzing mode skips the DAST pipeline entirelyWhen
RUN_MODE=fuzzingis set, STAR routes straight into the fuzzing pipeline and returns — endpoint discovery, the cloud scan, and the DAST fix loop never run. TheFUZZER_*variables below take effect only in this mode, and this mode ignoresSCAN_SCOPE, auth, and the cloud scanner.
The thesis: find the unexpected, not OWASP
The guiding rule is simple: if a bug class has a payload list and a signature detector, that is DAST's job. Fuzzing mode deliberately ships no SQLi/XSS/SSRF/LFI oracles. Its value is the opposite — availability and process-integrity bugs that nobody wrote a signature for, found by evolving malformed inputs and watching the target break.
Concretely, it looks for:
- Process-integrity faults — the program crashes, aborts, or hangs.
- Native memory-safety bugs — surfaced through sanitizers (ASan/UBSan) and compiler/stdlib hardening.
- Unrecoverable managed-runtime faults — fatal errors and traps across Go, Node, Rust, Crystal, and .NET.
- Resource-amplification DoS — a small input that drives disproportionate memory or CPU use.
It does not report SQLi, XSS, SSRF-as-payload, LFI/RFI, prototype pollution, or payload-driven command injection — those belong to the DAST modes, and the fuzzer refuses to build sink detectors for them. Round-trip/differential correctness mismatches and wrong-exception-type nitpicks are treated as noise, not vulnerabilities.
The fuzzer underneath
The engine is a standalone, coverage-guided, evolutionary mutation fuzzer written in Crystal — a separate, licensed binary, not part of STAR itself. Three properties make it suited to STAR's job:
- Evolutionary (genetic-algorithm) search. It starts from a valid seed, evolves a population of mutated inputs, scores how the target reacts (coverage depth, edge rarity, timing, resource gradients, crash severity), and keeps the mutations that push the target toward misbehavior.
- Coverage-guided. Edge-coverage ids reported by the harness are the primary steering signal, with AFL-style hit-count bucketing — the search rewards inputs that reach new code. (This is why STAR requires fuzzer
>= 0.24.0: older builds can't read the coverage tokens the harnesses emit and would fuzz blind.) - Structure-aware via a parser library (MultiParse). It parses many formats — JSON, XML, form, query, multipart, GraphQL, protobuf/gRPC, and ~17 magic-byte-recognized binary families like PNG/GIF/ZIP/HEIF — into a typed field tree and re-serializes byte-exactly. Mutations target individual fields while keeping the input structurally valid, so inputs land already past the parse and validation gates — exactly where the unexpected bugs live.
The fuzzer is protocol-agnostic: it doesn't know what your function does. It drives an external harness program over a small framed IPC protocol and classifies the result purely by how that harness process behaves — exit code, signal, timeout, memory, sanitizer text. That behavioral oracle is what makes it language-agnostic. This is function/binary fuzzing of parsers, decoders, and transformers — not web-app payload fuzzing.
End-to-end flow
flowchart TD
Enable["RUN_MODE=fuzzing<br/>(+ optional FUZZER_*)"] --> Resolve["Resolve fuzzer binary<br/>FUZZER_BIN → PATH → download<br/>fuzzer-linux-x64 ≥ 0.24.0, SHA-256 verified"]
Resolve --> Pick["LLM picks input-handling functions<br/>(breadth-first, robustness-oriented)"]
Pick --> Harness["Build & verify per-function Docker harness<br/>(must compile, accept seed, report steering coverage)"]
Harness --> Probe{"Seed clean &<br/>steering coverage?"}
Probe -->|no| Harness
Probe -->|yes| Fuzz["Fuzz: evolve structure-aware inputs,<br/>stream faults as NDJSON"]
Fuzz --> Confirm["Confirm resource findings<br/>(bisect-replay in fresh processes)"]
Confirm --> Triage{"3-axis gate:<br/>reachable + concrete impact +<br/>reaches process boundary?"}
Triage -->|harness defect| Harness
Triage -->|drop| Fuzz
Triage -->|keep| Fix{"FUZZER_FIX ≠ 0?"}
Fix -->|yes| Patch["Patch → rebuild →<br/>replay exact bytes →<br/>short-fuzz for siblings"]
Fix -->|no| Report
Patch --> Report["Finding in the PR:<br/>file:line + severity +<br/>replay recipe"]
Step by step:
- Enable. You set
RUN_MODE=fuzzing(plus optionalFUZZER_*). STAR parses the fuzzer config only in this mode, and the orchestrator routes straight into the fuzzing pipeline, skipping DAST entirely. - Resolve the fuzzer binary. STAR uses
FUZZER_BINif set, elsefuzzeronPATH, else downloads the prebuiltfuzzer-linux-x64release, verifies it against its published SHA-256, and caches it. Anything older than 0.24.0 is refused, because older builds can't read the coverage tokens the harnesses emit and would fuzz blind. - Pick targets. An LLM enumerates reachable, input-handling functions using a breadth-first, robustness-oriented selection prompt (not the DAST injection-sink prompt). In memory-safe languages it favors functions that leave the safe subset — native bindings, raw pointer work,
unsafe_*. Targets are deduplicated by name+signature and near-identical siblings per file are thinned. - Build and verify a harness per function. A harness is a tiny program that reads one input, calls the target, and crashes on an unexpected fault. The LLM builds it in two phases (a working harness, then real steering instrumentation), driving the exact build pipeline the run uses through dedicated build/probe tools rather than guessing. Where the language supports it, the harness is compiled with sanitizers (ASan/UBSan; Go
-race/-asan) so memory-safety bugs surface as crashes. A probe gate must pass before the harness is trusted: the seed must not fault, and the harness must report nonzero, steering coverage — otherwise the search is blind and the harness is rebuilt. Fidelity is enforced: no validation or guards the target itself lacks, no swallowed errors, no mocking away the risky path. - Fuzz. STAR runs each per-target harness image with
docker run; the fuzzer evolves inputs and streams one JSON finding per line (NDJSON). Faults are detected externally — process death, adaptive timeout, memory ceiling, sanitizer text in stderr — never self-reported by the code under test. Per-target budgets are bounded by LLM-rated complexity, and the loop stops starting new targets at the CI deadline. - Confirm resource findings. In persistent mode one warm process handles many inputs, so a raw memory or CPU reading isn't attributable to a single input. The fuzzer bisect-replays suspect inputs in fresh processes and reports only a payload that reproduces the spike alone, with isolated figures.
- Triage. An LLM applies a strict 3-axis gate to every fault. Harness-manufactured faults (under-sized buffers, broken mocks) are dropped and fed back into a harness rebuild; survivors get a calibrated severity and a reproducing payload.
- Fix and validate (unless
FUZZER_FIX=0). STAR generates a patch, rebuilds, replays the exact crashing bytes, and short-fuzzes the patched harness for a sibling fault before marking the fix validated and committing to the PR — up to five fix passes. - Report. Findings land in the same PR summary and run log as any STAR run, each with a file + function location, a severity with rationale, and a byte-exact reproduction command.
The 3-axis credibility gate
Most faults a fuzzer turns up are benign — a panic on garbage input isn't a vulnerability — and fuzzing mode is deliberately aggressive about dropping them. The bar is "each thing we report is a real security risk, not a code smell." A fault survives triage into a finding only when all three axes hold:
- Reachable from an external, untrusted entry point — triage traces callers backward to an HTTP route, CLI, file reader, queue consumer, or public library API.
- Concrete security impact — memory corruption, a real DoS, and so on, judged by the crash class rather than by severity label alone.
- Reaches the process boundary — only a named recovering code path (
contained) drops a finding;unrecoverable/escapes/unknownkeep it. Containment is unavailable for library code, since a library doesn't control its callers.
Faults manufactured by the harness itself — a buffer passed without its length, a mock that breaks a guarantee the real dependency provides, a precondition no real caller would break — are dropped as harness defects and rebuilt, not reported. Wrong-exception-type mismatches and plain input-rejection are dropped as robustness noise. This is why a fuzzing run reports few, high-confidence findings rather than a pile of crashes.
What it finds
Fuzzing mode reports four classes of fault, detected from how the harness process behaves (language-agnostic) and then narrowed by triage:
- Process-integrity faults — crashes, aborts, hangs (including algorithmic-complexity / ReDoS).
- Native memory-safety bugs (C/C++ and native code) — buffer overflows, use-after-free, and similar corruption.
- Unrecoverable managed-runtime faults (Go, Node, Rust, Crystal, .NET) — fatal errors and traps that end the process.
- Resource-amplification DoS — small inputs that drive disproportionate memory or CPU use.
It does not report the OWASP payload classes (SQLi, XSS, SSRF, LFI, …) — those belong to the DAST modes; see the thesis above.
Emerging / experimental detectorsTwo detectors are not guaranteed to be active in a default run: CPU-amplification (the memory-bomb detector is the reliable resource signal) and the syscall-escape oracle (zip-slip / SSRF-shaped / exec-shaped). Treat both as experimental until documented as active for your fuzzer version.
Severity mapping
| Finding class | Severity |
|---|---|
| Native memory corruption | Critical |
| Other crashes | High |
| Managed-runtime faults, out-of-memory, resource-amplification | High |
| Timeout / hang | Medium |
| Everything else | Low |
Triage can override the mechanical severity with a calibrated one, and drops anything that fails the 3-axis gate.
Configuration reference
Fuzzing mode is configured entirely through environment variables, consistent with the rest of STAR — there is no CLI flag or config file. The FUZZER_* variables take effect only when RUN_MODE=fuzzing.
| Variable | Meaning | Default |
|---|---|---|
RUN_MODE=fuzzing | Select fuzzing mode | — |
FUZZER_BIN | Explicit path to a fuzzer binary | resolve / download |
FUZZER_VERSION | Fuzzer release to download; must be ≥ 0.24.0 | latest |
FUZZER_EPOCHS | Override the adaptive per-function search budget | adaptive |
FUZZER_TIMEOUT | Per-input hang timeout, in seconds — exceeding it is a finding | 5 |
FUZZER_MAX_MEMORY | Per-input peak-memory budget — exceeding it is a finding | unset |
FUZZER_DEPTH | Multiplier that scales the execution budget | 1 |
FUZZER_MAX_TARGETS | Cap on total functions fuzzed in a run | 12 |
FUZZER_MAX_PER_FILE | Cap on near-duplicate sibling targets per file | 3 |
FUZZER_FIX | Set to 0 to find-and-report only (skip the fix loop) | fix on |
Breadth beats depthThe engine's own tuning lesson is that more targets at a smaller per-function budget finds more than fewer targets at a large one. Before cranking
FUZZER_DEPTHorFUZZER_EPOCHS, consider raisingFUZZER_MAX_TARGETSinstead so the run covers more of your input-handling surface.
Requirements and limitations
- Docker is required. Every harness is built and fuzzed inside a per-function Docker image on your CI runner; STAR cleans up the images it creates at the end of the run. See Prerequisites for the full runner requirements.
- Prebuilt binary is linux/x64 only. STAR downloads the
fuzzer-linux-x64asset; there is no prebuilt for other OS/arch. On other platforms, supply your own binary viaFUZZER_BIN. - The fuzzer is licensed. It performs a license check against a valid Bright API key (
BRIGHT_TOKEN) at startup — the one network call fuzzing mode makes to Bright. - No monorepo service selection.
RUN_MODE=fuzzingfuzzes the whole repository unless you explicitly name a service; it does not auto-select one the way the DAST modes can. - Findings are code locations, not HTTP endpoints. Each finding maps to a
file:line(plus the function) with a severity, a rationale, and a byte-exact reproduction recipe — afuzzer --persistent --replaycommand that re-feeds the exact crashing bytes against the harness. There is no HTTP request to replay, because nothing is served. This is the key difference from DAST findings, which point at HTTP endpoints. - No app startup, auth, or scan loop. Fuzzing mode never starts your application, detects auth, or opens a Repeater to Bright Cloud — it is fully local except for the binary download and the licensing check.
See also
- Intro — how STAR runs inside your CI/CD and the scan → fix → validate loop the DAST modes use (and which fuzzing mode intentionally skips).
- Capabilities & Use Cases — the run modes and the pipeline use cases.
- Run Modes & Scan Scope — the full
RUN_MODEreference, including the DAST modes fuzzing mode sits alongside. - Vulnerabilities Index — the DAST payload-class catalog that fuzzing mode deliberately leaves to the scanner.
Updated about 8 hours ago