DEV Community

CopperSunDev
CopperSunDev

Posted on • Originally published at coppersun.dev

Why an LLM Reviewer Isn't a Security Gate

BrassCoders' benchmark found that a frontier model issued zero proactive security warnings across six generation-mode tasks — the same code it later caught 12 of 12 bugs in when explicitly asked to review. That gap is the whole argument against treating an AI reviewer as a security gate.

A gate needs to run on every commit, automatically, without anyone asking. An LLM reviewer runs when invoked.

What a Gate Actually Means

BrassCoders runs identical scanners on identical code and produces identical findings on every run; that repeatability is what makes it a gate rather than a tool — a CI check either passes or fails consistently, and a team learns to act on it.

A gate has one defining property: it runs automatically and returns the same verdict on the same input. A test suite that passes on a green commit and fails on a failing one is a gate. A linter that fires on the same style violation every time is a gate. An AI reviewer that you ask for a review at code-review time runs when you invoke it, catches what it happens to catch that run, and produces different output on the next run of the same code. That's a useful tool. It's not a gate.

Security checks that only run when a developer remembers to invoke them miss the commits where nobody remembered. SQL injection ships on a Tuesday afternoon PR that had three reviewers and no one invoked the AI reviewer. BrassCoders runs on every commit where the CI step fires.

The Generation-Mode Probe

BrassCoders ran a generation-mode probe alongside its published benchmark: six neutral coding tasks with no mention of security, asking the model to write each from scratch. The model wrote clean code on most tasks — and issued zero proactive security warnings across all six.

The published benchmark post describes the finding: "the model issued zero proactive warnings while writing the code. It wrote the code and moved on. It flagged the problem only when asked to review."

The same model that caught all twelve planted bugs on explicit review introduced vulnerabilities during generation and said nothing. This isn't a criticism of the model. Generating code and reviewing code are different tasks. The model completes the prompt. Security review is a separate, explicitly invoked task.

The Determinism Problem for Security

BrassCoders returns the same B608 finding for user_lookup.py on every scan; an LLM reviewer returns varying findings depending on how the model samples that run.

This matters specifically for security. A CI gate that returns different verdicts on the same commit teaches developers to discount the result. If the SQL injection finding appears on Tuesday's scan and disappears on Wednesday's scan of the same code, the team learns to wait for a second opinion rather than act immediately. A security finding that needs a second opinion to trust has already lost its value as a gate.

A 2025 benchmark (Gnieciak and Szandala, arXiv 2508.04448) found LLMs scored higher recall than static analyzers but mislocated findings at the line-and-column level and generated more false positives. The determinism tradeoff is real: BrassCoders pins the exact line; the model coverage varies.

The Data-Handling Problem

BrassCoders' open-source core runs brasscoders --offline scan with zero outbound network calls and sends zero bytes off the machine; an LLM reviewer sends your source to an external API for every review.

For any codebase under data-handling constraints — internal tooling, financial records, healthcare data, code that processes personally identifiable information — the offline path is the one that runs on every commit. The LLM review path requires either a policy exception for every run or a self-hosted model. Neither scales as a gate.

The findings file BrassCoders produces contains only the scanner output: severity, file path, line number, remediation note. When an AI assistant triages findings from the .brass file, it reads the findings summary, not the source. For the triage step, your source stays local.

What the LLM Still Wins

The model caught one finding in BrassCoders' benchmark that no static analyzer caught: sum(readings) / len(readings) with no guard for an empty list — a ZeroDivisionError that leaves no structural pattern for any rule to match.

Logic bugs that require reasoning about edge cases belong to the model. A gate catches structural patterns; a model catches intent failures. The right pattern — recommended by the ZeroFalse paper and the Gnieciak and Szandala benchmark — runs the deterministic gate first and layers the model review on top. BrassCoders runs the gate; your AI editor handles triage.

pip install brasscoders
brasscoders --offline scan /path/to/your/project
Enter fullscreen mode Exit fullscreen mode

The gate runs. The model triages. You fix the findings that survive both.

Top comments (0)