Why autonomous reverse engineering needs a disprover
An agent that can only look for problems will always find them. The fix is not a better prompt: it is a second agent whose job is to fail the first.
A language model asked to find vulnerabilities in a decompiled class will find vulnerabilities in a decompiled class. It will find them in a logging helper, in dead code behind a flag that was never shipped, in a test fixture, in a library whose risky function is never called. The output looks like security work. Much of it is the model being agreeable.
This is the part of autonomous analysis that is easy to under-sell. The hard problem is not recovering the code. Decompilers, lifters and filesystem carvers have been good for years, and an agent that drives them is a straightforward engineering effort. The hard problem is that an autonomous system produces far more claims than a human would, and the cost of checking each one falls entirely on the reader.
Generators are not good critics of themselves
The obvious fix is to ask the agent to check its own work. In our testing so far it mostly does not help. The same context that produced the claim is the context used to evaluate it, and the same reasoning that found the sink finds a reason to keep it. Self-review catches formatting mistakes and the occasional arithmetic slip. It rarely catches the claim that is simply not reachable.
What does help is separation. We run a distinct agent, the Disprover, whose only objective is to refute a claim that has already been made. It does not score confidence or rank severity. It is given the claim and the evidence and asked to break it: find an alternate explanation for the same code, show the path is unreachable from any entry point, show the guard the first agent missed, show the environment condition the claim depends on is never true in a release build.
Framing matters more than it should. Asked "is this correct?", an agent tends to agree. Asked "find the reason this is wrong", it tends to look, and when it fails, it leaves a record of what it tried.
What a refutation looks like
In our testing so far, most refutations are mundane. A hardcoded key that turns out to be a well-known test vector. A permissive intent filter on an activity that is never registered in the shipped manifest. A weak cipher in a bundled library with no call site anywhere in the binary. A debug-only path behind a flag stripped at release. None is subtle, and each is the kind of claim a client's engineers will dismiss in minutes.
Refuted claims do not vanish. They are logged with the reason, and you can read them. We think that record matters as much as the findings: it shows what the system considered and rejected, which is the part of a manual review that normally stays in a reviewer's head.
The cost, and why it is worth paying
A disprover pass costs time and compute on every candidate, and throws away work the rest of the system just did. It can also be wrong in the other direction, which is why a claim it cannot break but nothing can confirm comes out labeled Unverified rather than silently dropped.
We accept that trade because the alternative is worse. A tool that reports twenty findings of which six are real is not a time-saver; it is a triage queue with extra steps. A tool that reports four findings, each with code, a call path and a command that reproduces it, is something a reviewer can act on the same day.
Autonomy without disproof is a faster way to generate work for someone else.