Reachability analysis is the gate, not the headline
Trace the vulnerable path from the report back to a callable entry point, then check whether the path survives contact with the real codebase. Large projects hide dead ends very well. A function can look relevant in a description and still sit behind a condition that never fires, or behind a path that needs state the test never creates.
The practical test is boring: identify the entry point, follow the call chain, and reject anything that only matches on names or stack traces. Code indexing helps cut the search space, but it does not prove reachability on its own. If the route from input to vulnerable operation is missing, the candidate belongs in the discard pile, not the PoC queue.
Trace the vulnerable path from report to entry point
Start with the reported bug shape, then map it to the program surface that can trigger it. That means looking for the actual handler, parser, command path, or service method that can feed the suspect code. A lot of false leads die here because the interesting function is real but unreachable from the exposed surface.
The useful question is not whether the code looks dangerous. It is whether an attacker-controlled path can reach it with the right state intact. If the path requires a private helper, test fixture, or setup sequence that the runtime never exposes, it is not a reproduction target.
Separate reachable code from code that only looks relevant
Static evidence often picks up nearby code that shares variables, types, or error messages with the vulnerable site. That is close enough to waste time and far enough to fail. Reachability analysis has to strip away the attractive noise.
A narrow call chain with clear input flow beats a broad guess about “related” code every time. When the evidence is thin, keep the candidate on hold until runtime behaviour confirms the path. Otherwise the workflow turns into a tour of the codebase, which is a very expensive way to learn that nothing interesting happens.
Building a proof-of-concept that survives differential validation
A proof-of-concept only matters if it behaves the same way on the vulnerable build and fails on the patched build in the expected way. Crashes that happen for the wrong reason do not count. Neither do assertion failures, environmental quirks, or inputs that happen to upset both builds.
Dynamic testing is the filter here. It checks whether the candidate input reaches the bug, whether the crash is stable, and whether the crash type matches the path being claimed. If the patched build falls over too, the candidate is not a clean reproduction. It is noise with a useful stack trace.
Use dynamic testing to confirm the crash belongs to the bug
Run the input against the vulnerable build and watch for the exact failure mode, not just any failure. Coverage, crash type, and stability all matter because a single odd run can lie quite convincingly. One crash does not prove the path is real if the next run behaves differently.
A valid PoC should keep hitting the same code path under controlled conditions. If the failure changes shape across runs, or appears in unrelated code, the input is not ready. Runtime behaviour is the only part that gets a vote once the static case has been made.
Keep task boundaries and runtime controls tight enough to trust the result
Multi-agent workflow helps when each agent has a narrow job and no excuse to wander. One agent can trace reachability, another can build the input, and a review step can check whether the crash matches the target path. That split only works if the boundaries stay sharp.
Isolated runtime controls matter because contamination ruins the result. Fresh environments, no cross-task memory, no access to hidden artefacts, and no stray tools reduce the chance that an agent “finds” a reproduction by accident. If the environment leaks old state or outside data, the output stops being trustworthy. The same applies to review: a candidate that cannot survive a separate check has not earned acceptance.
Treat patched-build failures and unrelated crashes as rejection cases
Differential validation is not generous. If the patched build crashes too, the reproduction is broken. If the crash lands somewhere else, it is also broken. If the input only produces an assertion failure, that is usually a bad fit for acceptance unless the bug description calls for it.
Treating those cases as failures keeps the workflow honest. It is easy to get attached to an input that looks dramatic in logs. The patched build is the decent adult in the room. If it behaves the same way, the candidate is not demonstrating the vulnerability, only that the test harness can be annoyed.


