Contrast Labs Scanner determinism · Part two
Every scan takes a different path through your code
Select a run to compare its measured result.
Claude Security · Standard mode
Run one returned the most candidate findings: 29, at a cost of $250 in 37 minutes.
In May we published what AI security scanning actually costs. Three scanners on one codebase agreed on 5% of findings. One scanner run three times reproduced 17% of its own results. A $315 scan generated $128,000 in triage labor. The most common response we got was patience. The models were early. The next generation would fix it. To see those early results, here’s part one of the series.
We ran the same scanner three times against the same commit of the same application on the same day. No code changed between runs. Nothing changed at all except the clock.
We used Claude Security in Standard mode, now running on Mythos 5, against a modern 80,000-line application, 55,000 lines of which are Java. Three runs against the same commit on the same branch, each in an isolated project with no shared context, all in one day.
Run One returned 29 findings for $250 in 37 minutes. Run Two returned 15 findings for $225 in 30 minutes. Run Three returned 21 findings for $255 in 50 minutes. Total spend was $730 across 117 minutes of scanning.
One detail matters more than the model version. Part one tested a codebase that had already absorbed years of static analysis, dynamic analysis, instrumentation, bug bounty and annual pentests. We said at the time that this weeded out the easy findings and gave the models a real workout. Vendors took that framing and ran with it.
This codebase does not qualify. It is new. It is modern. It is AI-heavy service code nobody has picked over. This is the easy case, and the scanner still cannot agree with itself.
Three-run agreement was 12% in part two, compared with 17% and 25% in part one.
Select a model to inspect its result. Switch between the intersection rate and the percentage-point gap to Mythos 5. Part one used a different codebase.
Mythos 5 · 12% · Part two
Part one used a different codebase. Each percentage is the intersection of three runs, not a measure of accuracy.
| Study | Scanner | Intersection |
|---|---|---|
| Part one | Sonnet | 17% |
| Part one | Opus | 25% |
| Part two | Mythos 5 | 12% |
Part one used a different codebase. Intersection is agreement across three runs, not detection accuracy.
Twelve percent of the 42 candidate findings were reported by all three runs.
Part one measured the same thing the same way. The Sonnet-based scan reproduced 17% of its own findings across three runs. The Opus scan managed 25%. Same protocol, same style of codebase, same three-run intersection. The newest and most capable model in the series scored 12%.
Fifty-seven percent of the findings were reported by exactly one run and never seen again.
24 of 42 candidate findings appeared in only one run.
Select a segment or frequency label to see its count and percentage. Every percentage uses the same 42-candidate total.
5 of 42 candidates appeared in all three runs.
| Frequency | Candidate findings |
|---|---|
| One run only | 24 |
| Exactly two runs | 13 |
| All three runs | 5 |
42 unique candidate findings. Frequency groups are mutually exclusive. Candidates are not validated vulnerabilities.
Twenty-four of 42 findings are ghosts. They appeared once, in one report, and no subsequent scan of identical code ever mentioned them again. Another 13 appeared in two runs out of three. Whichever scan you happened to keep determines which two thirds of this list you work on.
This is the number that changed my read of the whole problem.
The third run added nine new candidates, after the second added four.
Move the slider or replay the three measured runs to see the cumulative candidates, cost and scan duration. No later runs are projected.
9 new candidates in run three$730 spent across 117 minutes
Each floating bar adds candidates not seen in earlier passes. Only three runs were measured; no fourth-run prediction is shown.
| Through run | New candidates | Cumulative candidates |
|---|---|---|
| Run one | 29 | 29 |
| Run two | 4 | 33 |
| Run three | 9 | 42 |
Cumulative counts deduplicate candidates across the runs observed so far. No later runs are projected.
After run One we had 29 unique findings. Run Two added four the first run never mentioned, bringing the total to 33. Run Three added nine more, bringing it to 42.
Run Three added more new findings than run Two did. On identical code. After $475 had already been spent looking at it.
The curve is not flattening. It is steepening. Forty-two is not the number of candidate issues in this application, it is the number we happened to reach after three passes, and there is no evidence in this data that a fourth pass would not add another nine. We do not know where this converges. Neither does the vendor, because nobody has published a convergence curve for any of these tools.
That single chart invalidates the entire premise of a scan. A scan is supposed to be an assessment of an application. This is a sample from a distribution, and we cannot see the distribution.
Each run starts with nothing. No memory of the previous run, no record of which files it opened, no note of which call paths it already traced. It picks a place to start, and every choice after that narrows what it will ever see. The first file it opens shapes which call it follows, which shapes what loads into context, which shapes how much is left to examine before the pass ends.
A traditional static analyzer walks the same abstract syntax tree in the same order every time.The traversal is fixed, so the conclusions are fixed. Change nothing and you get the same answer back, which is the entire reason anyone was ever able to build a baseline.
An agentic scanner decides where to go next.That decision comes out of a model, and 80,000 lines do not fit in a single reasoning pass, so the scanner has to choose which parts of your application it will actually look at. Choosing under that constraint is sampling. Three runs took three different walks through the same code and came back describing three overlapping but different applications.
That is what the convergence chart is showing. Run Three did not surface nine new issues because it tried harder. It surfaced them because it went somewhere the first two runs never went.
Setting temperature to zero does not fix this, and it is worth being precise about why. Tool call ordering and context assembly still vary in an agentic system, so the walk still moves. But grant the vendors a perfectly deterministic scanner tomorrow. You would get the same 29 findings every single time and never see the other 13. Determinism without coverage is a consistent blind spot, which is easier to build a program on and no safer to trust.
This is where context-window constraints hit real-world application architecture. Complex vulnerabilities rarely live in a single method; they require tracing untrusted data flows across multiple files and layers—from an API controller, through middleware, down to a database query. Because LLM-based scanners must drop and assemble context to stay within reasoning windows, they inherently fragment cross-file data-flow tracking. In contrast, runtime instrumentation continuously observes data execution in memory as code runs, requiring no context-window sampling or path guesswork to trace an exploit.
The longest, most expensive run returned fewer findings than the first.
Switch between findings, cost and time to compare the three runs. Select a run to inspect its exact values.
29 findings · $250 · 37 minutes. The largest report.
| Run | Candidates | Cost (USD) | Minutes |
|---|---|---|---|
| Run one | 29 | 250 | 37 |
| Run two | 15 | 225 | 30 |
| Run three | 21 | 255 | 50 |
Three measured scans of the same commit. Summed report counts contain repeated candidates.
Run Three cost the most, ran 13 minutes longer than run one, and returned eight fewer findings.
You cannot buy your way out of this with more compute or more patience. There is no relationship between what you spend on a pass and what the pass gives you back. The most expensive, longest-running scan of the three was not the most thorough.
The number of HIGH severity findings changed from 11 to 4 to 4.
Select a bar to inspect a severity count. Switch to share of report to compare percentages using each run’s own finding count.
Each bar uses the same scale: 0–20 findings.
11 of 29 findings · 38% of the report
Counts show candidates, not confirmed vulnerabilities.
| Run | HIGH | MEDIUM | LOW | Total |
|---|---|---|---|---|
| Run one | 11 | 16 | 2 | 29 |
| Run two | 4 | 10 | 1 | 15 |
| Run three | 4 | 11 | 6 | 21 |
Severity counts are per report. Percentages in the chart use each report’s total, not the 42-candidate union.
Run one reported 11 HIGH severity findings. Runs Two and Three reported four each.
If you scanned this application once and took the output into a risk review, the answer to “how exposed are we” is either eleven serious problems or four, depending on which pass you were handed. Run Three also pushed six findings into LOW, three times as many as run one rated that way.
Sixteen findings were rated HIGH by at least one of the three runs. Seven of those sixteen were reported by a single run and never reproduced. Genericized, they include an authentication bypass by spoofing, a missing authentication control on a credit tracking controller, a second missing authentication control on a balance management controller, an integer overflow, a resource exhaustion condition, an improper cleanup on a thrown exception and a business logic error. CWE-290, CWE-306 twice, CWE-190, CWE-770, CWE-460 and CWE-841.
Five of those seven came from run One. Two came from run Three. Run Two contributed none. If you ran this scan once, you had a two in three chance of never hearing about any given one of them.
Only 2 of 42 candidates appeared in every run with unchanged severity.
Choose a consistency requirement to see how many candidates remain. Marks represent aggregate counts, not individual issue identities.
42 unique candidates across the three reports.
| Requirement | Candidate findings |
|---|---|
| All unique candidates | 42 |
| Seen in at least two runs | 18 |
| Seen in all three runs | 5 |
| Seen in all three with unchanged severity | 2 |
These are nested requirements, not mutually exclusive groups. Consistency does not establish correctness.
Eighteen findings appeared in more than one run. Of those, six held the same severity, nine held the same CWE and six kept the same category name. Twelve of eighteen were rated differently depending on which report you read.
Narrow it to the five findings all three runs agreed existed, the reproducible core of this application, and the labels still move. Two of the five held a stable severity. One went HIGH, MEDIUM, MEDIUM. Another did the same. A missing authorization finding went LOW, then MEDIUM, then back to LOW. It bounced.
The CWE moved too. A client-side enforcement issue was CWE-602 in run one and CWE-807 in ru
This state churn directly pollutes your Vulnerability Management systems. When findings bounce between CWE-602 and CWE-807, or bounce from HIGH to LOW, your ASPM (Application Security Posture Management) deduplication logic fails. Instead of updating an existing record, the system creates duplicate Jira tickets for the exact same underlying issue. This doesn't just create noise; it corrupts your Mean Time to Remediate (MTTR) metrics and generates cascading ticket bloat for the triage team.
Two findings out of 42 held both their existence and their severity across all three passes. That is the stable core of this assessment. Two.
This state churn directly pollutes your Vulnerability Management systems. When findings bounce between CWE-602 and CWE-807, or bounce from HIGH to LOW, your ASPM (Application Security Posture Management) deduplication logic fails. Instead of updating an existing record, the system creates duplicate Jira tickets for the exact same underlying issue. This doesn't just create noise; it corrupts your Mean Time to Remediate (MTTR) metrics and generates cascading ticket bloat for the triage team.
The same instability shows up at the file level, where no finding-matching rule is involved at all.
Only 8 of 28 source files were flagged in all three runs.
Select a frequency group to inspect how often files were flagged. Switch between file counts and percentages of the 28-file total.
8 of 28 source files · Every time
| Frequency | Source files |
|---|---|
| One run only | 14 |
| Exactly two runs | 6 |
| All three runs | 8 |
28 distinct flagged source files. These are file counts, not finding counts.
Twenty-eight source files were flagged across the three scans. Eight were flagged every time. Fourteen were flagged once.
If you are rolling any of this up to the OWASP Top 10, to ASVS, or into a PCI or SSDF control narrative, you are rolling up noise. The category your compliance report depends on is itself a sample.
You cannot verify a fix. That is what changed my mind between part one and part two, and it worries me more than any number above.
Part one argued that unstable findings break your trend lines. That was too polite. The problem is more basic than reporting.
Walk through it. A developer picks up a HIGH severity missing authorization finding. They read the code, add the check, write a test, open a pull request. You rescan to confirm the fix landed. The finding is gone.
Did they fix it?
You have no way to know. Twenty-four findings in this dataset showed up in one scan and never again, and nobody touched a line of code. When coverage is based on random sampling, a 'clean' rescan is functionally meaningless. It is impossible to distinguish a remediated vulnerability from an un-traced execution path. Your verification step cannot distinguish a fixed vulnerability from a finding this pass never went looking for.
Now run it backward. Run Three surfaced nine findings no previous scan had reported, in code that had not changed since the last two scans. In any normal workflow those read as regressions. Your team opens nine tickets to investigate code nobody has edited.
Every security automation pipeline assumes state continuity between scan N and scan N+1. Without deterministic execution paths, you cannot establish a verifiable baseline or programmatically confirm remediation. No new criticals in the merge gate. Risk acceptance with an expiry date. SLA clocks that start when a finding appears and stop when it clears. Every one of those controls requires that a finding staying, appearing or vanishing carries information.
At 12% reproducibility, none of them carry information.
Give credit where it is due. This generation is cheaper and faster per line of code than the last one. A single pass averaged $243 for 80,000 lines, roughly $3.04 per thousand lines against the $4.72 we measured in part one, in about half the wall clock time per line.
That improvement makes the reliability problem worse, because it removes the last excuse for running one scan and calling an application reviewed. If a pass is cheap, the honest thing to do is run several. Run several and you are staring at the convergence chart.
Getting to five reproducible findings on this application took $730 and 117 minutes. That is $146 per reproducible finding, none of which has been validated as real yet. For the two findings that held both existence and severity, it is $365 each.
None of the 42 findings has been confirmed as a true or false positive. That work is underway and it will be its own post. Everything above measures run-to-run consistency, not correctness. A finding all three runs reported is reproducible, not right, and a finding reported once may still be real. That is exactly the problem.
Our clustering rule was deliberately generous. Findings counted as the same issue if they shared a file path and either landed within five lines of each other or carried the same CWE, with at most one finding per run allowed into any cluster. A stricter rule produces worse agreement, not better.
The reproducibility comparison to part one is a like-for-like one. Twelve percent, 17% and 25% are all three-run intersections on a single codebase, measured the same way. We ran a third pass here specifically so that number would be comparable rather than convenient.
It finds things. That is the entire value proposition, and it is a real one.
These models reason about intent in a way that pattern-matching static analysis cannot. Broken access control, insecure direct object references and missing ownership checks across multi-tenant logic are genuinely hard for traditional tooling. The strongest confirmed true positives in part one came from AI scanners, and the one finding that survived all three runs here at a stable HIGH severity is an insecure direct object reference. That capability is real and it is worth having.
What it cannot do is tell you the same thing twice.For AppSec leaders, the path forward is positioning the tool correctly in the architecture: treat an AI scanner like an automated penetration tester, not a continuous build gate. Pen-testers excel at discovering novel, context-heavy logic flaws, but no architect would wire a human pen-tester into a merge gate to verify a bug fix on every pull request.
Use AI scanning for ad-hoc, periodic discovery. But for continuous verification, merge-gate enforcement, and establishing a stable risk baseline, you must rely on deterministic runtime execution that yields the same answer every time.
Do not make it a system of record. Wiring a non-deterministic scanner to a merge gate introduces probabilistic failure modes into your deployment pipeline. The gate logic is driven by sampling noise, not actual security posture. Do not track burndown from it, because the denominator moves on its own. Do not verify a remediation with it, because a clean rescan proves nothing. Do not put its severity ratings in front of a board as a measure of application risk, because we just watched those ratings bounce in both directions on code that never changed.
The gap this exposes is the one we keep landing on. Static analysis of any kind, deterministic or probabilistic, describes what your code might do. It cannot tell you what your application is doing in production right now, what is reachable by real traffic, or what is being probed. Runtime tells you that, and it tells you the same thing every time you ask.
Consistency is the property that makes every other property of a security tool useful. A scanner that answers differently each time you ask is a discovery aid with an invoice.
No. Twelve percent of findings reproduced across three identical runs, against 17% and 25% for the two approaches we measured in part one. All three figures are three-run intersections measured the same way. Run-to-run volume also swung wider, from 29 findings to 15 to 21 on unchanged code.
Because discovery is not the hard part of application security. Verification is. If a finding can vanish between identical scans, a clean rescan is not evidence that anyone fixed anything, and every control built on scan-to-scan comparison stops working. That includes merge gates, burndown reporting, risk acceptance expiry and remediation SLAs.
Not on this evidence. Run Two added four findings no previous scan had seen. Run Three added nine. The union of findings is growing faster with each pass rather than settling, so 42 is a floor rather than a total. More passes buy more coverage at linear cost with no visible convergence point.
Not on a hosted commercial scanner, and it would not be enough. An agentic scanner chooses which parts of a codebase to examine, because 80,000 lines do not fit in one reasoning pass, and tool call ordering and context assembly vary regardless of sampling temperature. A perfectly deterministic scanner would also just return the same arbitrary subset every time. That is a consistent blind spot rather than a fixed one.
Not yet. This research measures run-to-run consistency only. False positive analysis is in progress and will publish separately. Part one found that a meaningful portion of high and critical findings across all three scanners tested were outright false positives or safe-in-context patterns.
About $243 per pass for 80,000 lines, or roughly $3.04 per thousand lines, in an average of 39 minutes. That is cheaper and faster per line than part one. Three passes on one application cost $730 and produced five reproducible findings.
>Discovery, particularly authorization and access control flaws that pattern-matching analysis misses. Not as a system of record, not as a merge gate, not as remediation verification and not as an executive risk measure. For a stable picture of what is actually exposed, you need runtime visibility.
Take the full article and all seven chart tables into your own conversation.
Copies only this study and your question. You choose when to paste and send it.
Then open your assistant and paste: