Skip to content

Coverage-Guided Fuzzing

TopoExec keeps the default test gate deterministic, but graph input fuzzing now has an optional target that can run with libFuzzer or as a standalone corpus replay executable.

What is covered

fuzz_graph_inputs feeds arbitrary bytes through the YAML graph loader, defensive parser limits, graph structure validator/compiler, plan JSON renderer, Mermaid renderer, and lifecycle-order helper when a graph validates. This covers the current parser/compiler path for:

  • YAML loader failures and UTF-8 rejection;
  • schema-v1 shape and strict-field checks;
  • endpoint and trigger-policy parsing through graph validation;
  • immediate-cycle and CompositeLoop compile paths.

It does not instantiate components or run external adapters.

Local smoke

Use the portable standalone engine when libFuzzer is unavailable:

TOPOEXEC_FUZZER_ENGINE=STANDALONE ./scripts/fuzz_smoke.sh

This configures build-fuzz, builds fuzz_graph_inputs, and replays the seed corpus under tests/fuzz/corpus/graph_inputs through CTest.

When Clang/libFuzzer is available, run a coverage-guided smoke:

CXX=clang++ TOPOEXEC_FUZZER_ENGINE=LIBFUZZER ./scripts/fuzz_smoke.sh

The CMake option is explicit and off by default:

cmake -S . -B build-fuzz \
  -DTOPOEXEC_BUILD_FUZZERS=ON \
  -DTOPOEXEC_FUZZER_ENGINE=LIBFUZZER
cmake --build build-fuzz --target fuzz_graph_inputs -j
ctest --test-dir build-fuzz --output-on-failure -R fuzz_graph_input_target_smoke

TOPOEXEC_FUZZER_ENGINE=AUTO selects libFuzzer on Clang and standalone replay on other compilers.

Corpus regressions

If a crash is found, minimize it with the fuzzer toolchain, then commit the smallest reproducer under tests/fuzz/corpus/graph_inputs/ with a descriptive name such as crash_2026_05_06_invalid_anchor.yaml. Corpus files may be invalid YAML or invalid UTF-8; write binary seeds with a script when needed.

Keep seeds small, deterministic, and dependency-free. Do not add private data, credentials, host-specific paths, or large generated corpora to the repository.

CI policy

Default CI remains scripts/agent_check.sh. The libFuzzer smoke is a separate Clang job so fuzzer instrumentation does not change the normal package/runtime builds. Longer fuzz campaigns should run outside the default PR gate and commit only minimized regression seeds.