RedMirror Reflection is now the default way to use RedMirror — one offline binary that gives your own coding agent the power to find real bugs and prove them, right on your machine. Get started →
Perspective AI code review Hallucination

AI code review hallucinates. Proof doesn't.

An LLM reviewer generates the most probable review comment, not the true one. So it invents bugs that are not there, writes reproduction logs that never ran, and, worse, calls insecure code secure. You cannot prompt your way out of this, because the failure is the model, not the phrasing. The fix is to stop grading the model's prose and start proving whether the bad state is actually reachable. Findings should be proofs, not opinions.

Ask a language model to review a diff and you get a fluent, well-structured, confident answer. That is the problem, not the feature. The model was trained to produce text that reads like a good review, and it will do that with equal conviction whether the bug is real, imaginary, or sitting right there unmentioned. The output looks like the work. It is not the work.

Why does it hallucinate in the first place?

A reviewer built on a language model is a probability machine over tokens. Given your code, it emits the most likely continuation: a comment, a line number, a severity, a CVSS vector, sometimes a whole reproduction transcript. Every one of those is just text. Nothing behind the words executed your code, reached the function, or tried the input. There is no ground truth in the loop to stop the model from writing a completely convincing report about a bug that cannot happen. Fluency and truth are separate variables, and the model only optimizes one of them.

This is the same complaint the security community is now making out loud. Writing about a wave of AI-generated vulnerability reports, the RTCSec News newsletter described submissions arriving with scary scores attached to generated text and hallucinated reproduction logs, and landed on a rule for the field: reproduce before you rate. Its verdict on the tooling was fair: AI is a strong assistant that still, in its words, "needs a lab and a skeptic". That is exactly right.

The two ways it hallucinates

The confident false positive. The reviewer invents a vulnerability, points at a real line, assigns a plausible severity, and can even hand you a reproduction that never ran. You spend an afternoon chasing it before concluding there was nothing there. Do that twice and the team learns the lesson every noisy scanner teaches: ignore the tool. A finder you have trained yourself to ignore has negative value.

The confident false negative. You ask "is this secure?" and get back "this looks secure." It is the same machine doing the same thing, generating the most probable text, and reassurance is very probable text. This is the dangerous one, because the bugs that matter most do not look wrong on the page.

Why "looks secure" is the expensive hallucination

A broken authorization check does not throw. An IDOR where changing /orders/1001 to /orders/1002 returns someone else's order returns a normal, healthy response. A missing tenant filter hands back the whole table as clean JSON. None of it looks like a bug in a diff, so a model reading the diff says it looks fine, and it is not lying by its own lights: the text really is the most probable text. The catch is that "most probable given the training data" and "safe in your deployed app" are not the same claim, and the reviewer cannot tell them apart. A negative it never actually checked is a hallucination wearing a reassuring face.

You cannot prompt your way out of it

The instinct is to fix this with words: a sterner system prompt, a rubric, a "think step by step," a second model to grade the first. None of it addresses the cause. A better prompt produces more fluent text, not more true text. A second reviewer hallucinates the same way the first one did, and two confident narrators agreeing is not evidence, it is a chorus. As long as the thing being trusted is prose, you are grading how a report reads, not whether the bug exists. The only way out is to move the deciding step outside the model entirely.

What replaces the opinion: a reachable path

RedMirror does not ask a model to be trusted. The model proposes where a bug might live, which is the genuinely hard, human part of the job. Then a compiled kernel takes over and does something a language model structurally cannot: it lifts the code to a state machine and searches the reachable states for a way to violate the property. It either returns a concrete, replayable path into the bad state or it returns nothing. There is no confidence score to inflate, because there is no opinion involved. Here is the shape of what comes back, instead of a paragraph:

invariant: only_the_owner_reads_a_record  (viewer_id == record.owner_id)
REFUTED
  1. <init>                     session.role = "user", viewer_id = 8137
  2. GET /api/orders/1002        record.owner_id = 4471   // not the viewer
  3. handler returns 200         viewer_id != owner_id   // invariant broken, path returned

You can rerun those steps. A hallucinated report cannot survive that, because you cannot replay a paragraph. Text is cheap to generate and impossible to reproduce on demand; a path either drives the program into the bad state or it does not. That single property, reproducibility, is what separates a finding from a good-sounding guess.

Does this make the model useless? No.

The opposite. Splitting the roles is what finally makes the model safe to lean on. Let it be the proposer, generating candidates and hypotheses as fast as it likes, and let the kernel be the judge that proves or throws each one out. The model's job stops being "be right" and becomes "be interesting," which is a job it is genuinely good at. We built the whole product around that split, and tested it directly: four different LLMs, one harness, and a compiler grading every answer they produced. Across a hundred runs it let through two false alarms. The model does not have to be trustworthy when the thing behind it will not pass an unproven claim.

So the honest scope. This does not verify everything, and it does not pretend to. It proves reachable logic, authorization, and state bugs, the class that hides behind a clean-looking diff, and it reproduces taint it can drive to a sink. It is not a secrets scanner or a dependency checker, and where the model is only proposing, it can still propose nonsense. The difference is that the nonsense never reaches you wearing the word "finding." Everything the kernel could not prove is filed as a candidate with the reason it was ruled out, which is a far less exciting thing to publish and a far more useful thing to receive.

Frequently asked questions

Why does AI code review hallucinate?

Because a language model predicts the most probable next tokens, not the true ones. A review comment, a severity, a CVSS vector, and a reproduction log are all just text it can generate fluently whether or not the bug is real. Nothing in the model executes the code, so there is no ground truth to stop it from writing a convincing report about a bug that does not exist.

Can I fix hallucinated findings with a better prompt or a second model?

No. Asking the same class of model to double-check its own work produces more fluent text, not more truth, and a second reviewer hallucinates in the same way the first one did. The failure is in trusting prose as evidence. You need something outside the model that checks whether the claimed bad state is actually reachable in the code.

What is the difference between a hallucinated finding and a proof?

A hallucinated finding is a paragraph that resembles a bug report. A proof is a concrete, replayable sequence of steps that drives the program into a state it should not reach, produced by searching the reachable state space. Text can be faked; a path that replays cannot. RedMirror only calls something a finding when such a path exists and reproduces.

Does this mean LLMs are useless for finding security bugs?

No. A model is a strong proposer of where to look, which is the expensive human part. The mistake is letting it also be the judge of whether the bug is real. RedMirror splits the roles: the model proposes, a compiled kernel disposes by proving or refuting the path. In an internal test across four models graded by the kernel, that design produced two false alarms in a hundred runs.

Is 'the code looks secure' a hallucination too?

Yes, and it is the more expensive one. The bugs that matter most, like broken authorization and IDOR, do not look wrong on the page, so a fluent 'this looks secure' is exactly the answer a probability machine will give. A confident negative that was never checked is a hallucination wearing a reassuring face.

Stop grading prose. Start proving paths.

RedMirror Reflection runs inside your coding agent, on your machine, with your own model. The agent proposes, the kernel proves, and only a reachable, replayable path is ever called a finding.

Get RedMirror How do I know if my app is secure?