User Guide
Forge-CRS — User Guide
Running a campaign
From app/:
node bin/crs.mjs run
You'll get a table like:
STATUS TARGET CWE EXECS PoV PATCH/REG
------------------------------------------------------------------------------
OK config-merge CWE-1321 3 53->31B fixed 2/2
OK path-store CWE-22 189 14->2B fixed 2/2
OK task-runner CWE-78 14 22->1B fixed 1/1
OK regex-validate CWE-1333 40 31->23B fixed 4/4
OK binary-reader CWE-125 5 6->2B fixed 2/2
How to read it:
- STATUS —
OK= REMEDIATED (discovered + classified + patched + PoV
neutralized + regression preserved). PART = partial, MISS = not found, FAIL = patch failed.
- EXECS — how many test cases the fuzzer executed before it tripped the
oracle. Lower means the bug was easy to reach; this is a real search, so it varies by target.
- PoV — proof-of-vulnerability size *before → after* minimization. The
minimizer shrinks the random crashing input down to the essential trigger (e.g. path traversal minimizes to just ..).
- PATCH/REG — whether the synthesized patch neutralized the PoV, and how
many functional regression cases still pass.
Useful flags
| Flag | Effect |
|---|---|
--seed <n> | Set the fuzzing seed (campaigns are fully deterministic per seed). |
--target <id> | Run a single target (config-merge, path-store, task-runner, regex-validate, binary-reader). |
--report | Write the full structured campaign to .work/campaign-report.json. |
--quiet | Print only a machine-readable summary line (for CI). |
Exit code is 0 only if the full pipeline remediated every target.
The full report
--report emits, per target: discovery stats (executions, iterations, coverage blocks), the minimized PoV, the classified CWE, the **unified-style patch diff**, and the per-case regression results. This is the artifact to attach to a ticket or audit.
Adding your own target
The engine is target-agnostic; all knowledge lives in app/src/registry.mjs. A target entry provides:
file/entry— the source file and exported function under test.kind—in-process(coverage-guided) orworker(time-bounded, for hangs).mutateKind—string|buffer|json(selects the mutator family).seeds— initial corpus.makeContext()/makeArgs(value, ctx)— turn a fuzz value into a real call.oracle(exec, ctx, value)— decide if a result is a real vulnerability
(return { vuln, signal, evidence }).
regression— functional cases any patch must keep passing.patch—{ find, replace, summary }semantic source rewrite.
Drop a vulnerable module in app/targets/, add a registry entry, and node bin/crs.mjs run --target <id> will attack and repair it. The oracle and the patch are deliberately decoupled: discovery never sees the fix, and the patch is never trusted until the validator re-proves the PoV is dead.
Safety
No real exploit is ever executed: the filesystem read and process spawn for the traversal/injection targets are dependency-injected recorders, so the CRS observes what *would* happen without touching your machine. ReDoS candidates run in a disposable worker the parent can hard-kill.