Language Runners¶
TokenFuzz supports C/C++, Rust, Go, Python, Java, and other ecosystems behind one probe and triage contract. This page covers the part that changes outside ordinary C/C++: how a testcase reaches the audited package, and which runtime diagnostics count as crash evidence.
The registered ecosystems are:
- Rust, Go, Swift;
- Java, Kotlin;
- Python, Ruby, PHP;
- JavaScript / TypeScript (Node);
- Perl, R;
- any other ecosystem with an explicit
[runner]command.
Choose the runtime posture¶
Does the target have a sanitizer build?
├── Yes → [sanitizer] enabled = ["asan", …]
│ confirmed sanitizer/race evidence can become a crash bundle
│ non-crash security issues remain findings
│
└── No → [sanitizer] enabled = []
the configured runner executes testcases
runtime diagnostics guide investigation but are not auto-filed
the agent files a finding only after establishing security impact
A genuine sanitizer or race diagnostic is still crash-class evidence when a runner emits it, but it must satisfy the same confirmation and bundle requirements as any other crash. An ordinary exception, panic, or traceback is not sanitizer evidence and is not a security finding by itself.
bin/setup-target picks a conservative default by introspecting the source
tree (Cargo.toml, go.mod, pyproject.toml, package.json, and so on). For
a recognized non-native ecosystem with no configured sanitizer route, that
default is findings-only:
[sanitizer] enabled = [];- a starter
[runner].
Opt into race or another sanitizer by editing the shared
output/<target>/target.toml between runs, then start a new session. A running
session reads its pinned .target.toml snapshot.
What sanitizers exist per language¶
| Language | Compile-time flag | Sanitizer slug |
|---|---|---|
| C / C++ | -fsanitize=address / undefined / memory / thread |
asan, ubsan, msan, tsan |
| Rust | RUSTFLAGS="-Z sanitizer=address" (nightly) |
asan; also tsan and msan on supported targets |
| Go | go build -race |
race |
| Swift | swift run … -Xswiftc -sanitize={SWIFT_SANITIZER} through the seeded runner |
asan, ubsan, tsan |
| Java / JVM | None for JVM code; a JNI library can be built with ASan and driven separately | None for the JVM. Substantive security issues use findings/. |
| Python | An ASan-built C extension driven by a standalone harness (see samples/sample-python-native) |
Optional asan for native extensions |
| Node / V8 | No compile-time sanitizer for ordinary JavaScript; native add-ons can link ASan | Optional asan for native add-ons |
| Everything else | None; findings-only mode is the right choice | n/a |
When a sanitizer is available, enable the slug and configure its execution
route. Swift selects its sanitizer through runner tokens, and Go race uses
the runner; neither follows the ordinary native <name>_bin rule.
What target.toml looks like for each ecosystem¶
bin/setup-target seeds these automatically for ecosystems in its language
registry. A configured findings-only target has the same outer shape:
[sanitizer] enabled = [] plus a [runner] block naming the interpreter or
driver. A Python target, fully annotated:
target = "demo"
build_system = "python"
[sanitizer]
enabled = [] # findings-only mode
[runner]
bin = "python3"
args = ["{TESTCASE}"]
env = [
"PYTHONDEVMODE=1",
"PYTHONPATH={TARGET_ROOT}:{TARGET_ROOT}/src:{TARGET_ROOT}/lib",
]
crash_patterns = [ # seeded from the language registry
"Traceback \\(most recent call last\\):",
"MemoryError",
"RecursionError",
"SystemError",
"Fatal Python error:",
"==\\d+==ERROR: AddressSanitizer",
]
The other ecosystems differ only in the [runner] fields:
| Ecosystem | build_system |
bin |
args |
Notable env |
|---|---|---|---|---|
| Python | python |
python3 |
["{TESTCASE}"] |
PYTHONDEVMODE=1, PYTHONPATH={TARGET_ROOT}:{TARGET_ROOT}/src:{TARGET_ROOT}/lib |
| Go | go |
go |
["run", "{TESTCASE}"] |
GOFLAGS=-mod=mod, GORACE=halt_on_error=1 |
| Rust | cargo |
cargo |
["run", "--quiet", "--manifest-path", "{TARGET_ROOT}/Cargo.toml", "--", "{TESTCASE}"] |
CARGO_HOME={TARGET_ROOT}/.audit/cargo-home, CARGO_NET_OFFLINE=true |
| Swift | swift |
swift |
["run", "--quiet", "--disable-sandbox", "--skip-build", "-c", "release", "-Xswiftc", "-sanitize={SWIFT_SANITIZER}", "-Xswiftc", "-O", "--scratch-path", "{TARGET_ROOT}/.audit/swift-build-{SWIFT_SANITIZER}", "--package-path", "{TARGET_ROOT}", "{TARGET_SLUG}", "{TESTCASE}"] |
none |
| Ruby | bundler |
ruby |
["{TESTCASE}"] |
RUBYLIB={TARGET_ROOT}/lib |
| Java / JVM | maven or gradle |
java |
["{TESTCASE}"] |
none |
| Kotlin | kotlin |
kotlinc |
["-script", "{TESTCASE}"] |
none |
| Node | npm |
node |
["{TESTCASE}"] |
none |
| PHP | composer |
php |
["{TESTCASE}"] |
none |
| R | rlang |
Rscript |
["{TESTCASE}"] |
R_LIBS_USER={TARGET_ROOT}/.audit/r-library |
| Perl | perl |
perl |
["{TESTCASE}"] |
PERL5LIB={TARGET_ROOT}/lib |
TypeScript projects are detected as npm and receive the Node runner. A
project whose testcases must be TypeScript sets bin to its own loader; the
committed samples/sample-typescript uses ts-node.
For Swift, audit preflight builds every enabled release sanitizer configuration
whose route uses the Swift runner with --skip-build, each in
.audit/swift-build-<sanitizer>. That keeps compilation out of each testcase's
15-second execution budget and stops audits on different sanitizers from
replacing each other's products. A configured sanitizer binary still owns its
route and does not pay for an unused Swift build. Preflight builds only
--product <name>, where <name> is the executable [runner].args names
before {TESTCASE}, so unrelated test-support targets stay out of the build,
and a package whose product is named differently from the slug needs only that
one edit.
bin/setup-target writes the matching starter [runner] block for each
recognized registry ecosystem, and --build then proves that block reaches the
target: it runs one generated testcase in the target's own language through
bin/probe, and fails setup if the runner executed outside targets/<slug>/
or resolved its imports entirely outside it. bin/audit and bin/benchmark
repeat that check before spending a model on the target, so a runner that
starts but loads an installed copy of the audited package is rejected instead
of auditing the wrong code. The check stands aside, and says so, when it cannot
make that claim: a Cargo root package that exposes no library for the canary to
depend on, a changed [runner].bin or args, or configured [sanitizer]
binaries that own every enabled testcase route, because the registry's
generated source is then no longer proof of what runs. An unrecognized build
system does not receive a guessed runner; configure its [runner] explicitly.
To print the registry's current answer for any build system:
There is a worked example for every language here
Rather than starting from the table, copy a config that is known to run.
The repository ships a configured synthetic target for each of these
ecosystems under targets/samples/sample-*, with its hand-authored
target.toml committed at output/samples/sample-*/target.toml. See
Sample targets.
A few ecosystem notes:
- Go seeds findings-only
go run. To use the runtime race detector, set[sanitizer] enabled = ["race"]andargs = ["run", "-race", "{TESTCASE}"], or point the[runner]at a pre-builtgo build -racebinary (thesamples/sample-gotarget demonstrates the latter route). - Rust: a library-only crate has no
cargo runroute. Write the testcase as a direct.rsfile calling the crate's public API, or a// HARNESS: <name>.rsdriver beside an opaque input;bin/probebuilds either against the audited crate in release mode (matching the bootstrap build, sodebug_assert!is not mistaken for a finding).bin/setup-target --buildprefetches dependencies into.audit/cargo-home, which those builds then read offline. - Rust can opt into an AddressSanitizer build: set
[sanitizer] enabled = ["asan"], pointasan_binat the instrumented binary, and commit a.audit/build.shthat produces it with a nightly-Zsanitizer=address -Zbuild-stdbuild.bin/setup-target --buildmaterializes it (see thesamples/sample-rusttarget). - Swift is the exception to the "non-native means findings-only" rule in
the decision tree: its seeded
[runner]compiles the package with-sanitize={SWIFT_SANITIZER}, so a sanitizer diagnostic routes tocrashes/like a C/C++ target rather than staying findings-only. - Java: single-file Java is supported (JEP 330), so
java <file.java>compiles and runs in one shot. This is the seeded default. When seeding,bin/setup-targetprefers a working JDK fromJAVA_HOME, then a workingjavaonPATH. - Kotlin:
build_system = "kotlin"seeds script-style.ktsprobes. Plain.ktsidecar harnesses compile throughkotlinc -include-runtime. A detectedgradlebuild currently receives the Java JEP 330 runner (java {TESTCASE}), not the Kotlin script runner. For a Gradle/Kotlin target, either use a Java-interoperable testcase with the required target classpath or configure a project-specific Kotlin/Gradle runner explicitly; do not assume the generated Java command loads Kotlin application code. - R:
bin/setup-target --buildinstalls a package with aDESCRIPTIONmanifest into.audit/r-library, so a compiled component is built rather than skipped; the seeded runner pointsR_LIBS_USERat that target-local library. The install is a snapshot, so a laterbin/setup-targetwithout--buildreinstalls it when the checkout has moved since.
Crash and finding routing¶
Keep three stages separate: the probe verdict, the agent's filing decision, and triage's publication decision.
| Saved output | What bin/probe establishes |
Filing action |
|---|---|---|
| ASan, TSan, MSan, UBSan, or another accepted sanitizer diagnostic | Sanitizer-class evidence was observed on this execution route. | Confirm with bin/probe --confirm. On a native sanitizer route (CLI or compiled harness) the confirmed crash is filed under crashes/ automatically; for an interpreted sidecar harness or the runner route, the probe prints the crashes/ path and the agent files it. |
Go WARNING: DATA RACE |
Race-detector evidence was observed. | Same as a sanitizer diagnostic when race is enabled. |
A registered traceback, panic, exception, or fatal-error banner with [sanitizer] enabled = [] |
The runner produced a diagnostic worth investigating. | Trace it to source. The agent authors findings/FIND-* only for a concrete issue that crosses a security boundary. |
| No recognized diagnostic | Nothing to file. | Read the probe verdict (CLEAN, NO_EXEC, EXEC_FAIL) and its coverage column, then revise the testcase. |
In findings-only mode the probe route is runner. bin/probe still prints a
CRASH verdict for a recognised runtime banner so the investigator does not
miss it, but it never files a crash bundle for that route; the verdict is not a
filing decision.
Triage keeps the lanes honest afterwards. A crash directory that holds only a
managed-runtime diagnostic is relocated to findings/ when it carries a
substantive report and a reproducer; otherwise it is held pending and then
rejected. A crash directory on a sanitizer target that lacks the sanitizer
signal ends up in crashes-rejected/.
Writing harnesses in non-C/C++ languages¶
Name the sidecar with a HARNESS: header in the file's native comment syntax:
# HARNESS: in Python, // HARNESS: in C or JavaScript,
<!-- HARNESS: … --> in HTML. bin/probe reads the header fields from the
first 16 lines of the file, and any comment prefix without letters works
(//, #, ;, --, /*, <!--). The same rule applies to TARGET:,
MODE:, and PROPERTY:.
The file extension, not the header, picks the build-or-interpret path.
For the authoritative table, one row per language with its harness extensions and build systems, run:
The harness extensions split into two buckets:
# Compiled (cached binary): .c .cc .cpp .cxx .C .go .kt .rs .swift
# Interpreted (no build step): .py .rb .pl .php .js .mjs .ts .tsx
# .java .kts .r .R .sh .bash
Crash patterns¶
If your target has a project-specific runtime banner (for example, [BUG]
from a custom panic handler, or ASSERTION FAILED: from a debug build), add it
under [runner].crash_patterns:
[runner]
bin = "python3"
args = ["{TESTCASE}"]
crash_patterns = [
"^Internal compiler error:",
"^=== ABORT ===",
]
bin/setup-target seeds this list with the language's own runtime markers
(Traceback, panic:, Exception in thread, and so on), layered on top of
the built-in sanitizer patterns. Add to it only for a banner specific to your
project.
reproduce.sh templates¶
bin/export-repro writes a runnable reproduce.sh for crashes driven by a
browser/JS page, a CLI input (including Go race binaries), a recorded shell
wrapper, or a C/C++ sidecar harness. Sidecar harnesses in other languages
(.go, .rs, .swift, .kt, and the interpreted extensions above) run
through bin/probe but are not yet packaged by the exporter. See
Reproduce a crash for the script's checkout and build
contract.
See also¶
- Target config reference: the full
target.tomlschema. - Target configuration: the operator review workflow.
AGENTS.md(repository root): the agent-facing audit workflow, covering both browser and generic targets.