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 feeds 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.
The dividing line is simple: 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 ever 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. Instead it generates small test programs around your input-handling functions, exercises them with a standalone fuzzer in Docker, triages what breaks, and (optionally) fixes and re-validates it — all locally on your runner. The one call it makes to Bright is a startup license check against your
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.
How it works
Fuzzing mode is built around a single idea: find the unexpected, not OWASP. You point STAR at a repository, it decides what to fuzz and how hard, and you get back a short list of high-confidence faults — each tied to a line of code and a reproduction recipe. The flow is what you configure → what STAR does → what you get.
flowchart TD
Enable["You set RUN_MODE=fuzzing<br/>(+ optional FUZZER_* limits)"] --> Pick["STAR picks input-handling<br/>functions to fuzz"]
Pick --> Harness["Wraps each one in a<br/>small test program"]
Harness --> Fuzz["Fuzzes it with evolving,<br/>structure-aware inputs"]
Fuzz --> Triage["Keeps only faults that are a<br/>real, reachable security risk"]
Triage --> Fix{"FUZZER_FIX ≠ 0?"}
Fix -->|yes| Patch["Patches, rebuilds,<br/>and re-validates the fix"]
Fix -->|no| Report
Patch --> Report["Reports each finding in the PR:<br/>file:line + severity +<br/>reproduction recipe"]
What you configure
You set RUN_MODE=fuzzing and, optionally, a few FUZZER_* limits (budget, timeouts, how many targets). That is the whole interface — there is no CLI flag or config file, and the FUZZER_* variables are read only in this mode. See the Configuration reference below.
What STAR does
STAR decides what to fuzz: it finds the reachable, input-handling functions in your code and favors the risky ones — native bindings, raw pointer work, and anything that leaves a language's safe subset, where memory-safety bugs are most likely. It then wraps each function in a small test program (a harness) that feeds it one input and reports back whether the function broke, and verifies that harness is sound before trusting it. Where the language supports it, the harness is built with memory-safety sanitizers so corruption surfaces as a clean crash.
With the harnesses ready, STAR fuzzes each one: it evolves a stream of malformed-but-structurally-valid inputs and judges a fault purely from how the process behaves — a crash, a hang past the timeout, memory past the ceiling, or a sanitizer error — never from the code reporting on itself. That behavioral judgment is what makes fuzzing mode language-agnostic. Resource findings (a memory or CPU spike) are re-checked in isolation so a reported spike is attributable to one specific input, not to the warmed-up process.
Finally, STAR triages. Most faults a fuzzer turns up are benign, and fuzzing mode is deliberately aggressive about dropping them — the bar is a real security risk, not a code smell. A fault is kept only when all three hold:
- Reachable from an external, untrusted entry point — an HTTP route, CLI, file reader, queue consumer, or public library API.
- Concrete security impact — memory corruption, a real denial of service, and the like, judged by the nature of the crash rather than a severity label.
- Escapes the function — a fault that a named recovery path contains is dropped; one that ends or escapes the process is kept. (Library code can't assume containment, since it doesn't control its callers.)
Faults the harness manufactured itself — a buffer passed without its length, a mock that breaks a guarantee the real dependency provides — are dropped and the harness is rebuilt, not reported. Wrong-exception-type mismatches and plain input rejection are treated as noise. This is why a fuzzing run returns few, high-confidence findings rather than a pile of crashes.
What you get
Unless you turn the fix loop off (FUZZER_FIX=0), STAR patches each kept finding, rebuilds, replays the exact crashing input to confirm the patch holds, and does a short follow-up fuzz for sibling faults — up to five fix passes — before committing the fix to the PR. Findings land in the same PR summary and run log as any STAR run, each with a code location (file:line plus the function), a severity with its rationale, and a byte-exact reproduction command.
Under the hoodFor readers who want the internals: the engine is a standalone, coverage-guided, evolutionary mutation fuzzer written in Crystal — a separate, licensed binary, not part of STAR itself. It steers the search with edge-coverage signal from the harness (AFL-style hit-count bucketing), which is why STAR requires fuzzer ≥ 0.24.0 — older builds can't read the coverage tokens the harnesses emit and would fuzz blind. It is structure-aware through a parser library (MultiParse) that understands many formats — JSON, XML, form, query, multipart, GraphQL, protobuf/gRPC, and ~17 magic-byte-recognized binary families like PNG/GIF/ZIP/HEIF — mutating individual fields while keeping each input structurally valid, so inputs land past the parse and validation gates. It drives each harness over a small framed IPC protocol and streams one JSON finding per line (NDJSON). Resource findings are confirmed by bisect-replay in fresh processes. This is function/binary fuzzing of parsers, decoders, and transformers — not web-app payload fuzzing.
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.
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 three-part credibility bar above.
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 7 days ago