← All writeups
Research MethodologyMar 04, 2026· 7 min read

Reading 40,000 Diffs a Day: Inside Our LLM Variant-Analysis Pipeline

How we turned a firehose of open-source commits into a ranked queue of probable vulnerabilities using embeddings, an LLM security judge, and a sandbox that refuses to trust the model's optimism.

S1

@s1nk

LoSec Research

The premise

Most vulnerabilities are not novel — they are variants. A maintainer patches one missing bounds check and leaves nine siblings untouched across the codebase. A security fix lands quietly in main weeks before anyone writes an advisory. The window between silent fix and public CVE is where a lot of real-world exploitation happens, and it is almost entirely a reading problem.

So we built a pipeline that reads. At steady state it ingests roughly 40,000 commit diffs per day across the repositories we track and hands human researchers a short, ranked queue of the ones that smell like security. Here is how it works and, more importantly, where the LLM is not allowed to be in charge.

Stage 1 — Ingest and pre-filter

We pull commits, but we throw most of them away before any model sees them. Cheap heuristics kill the noise:

SECURITY_HINTS = re.compile(
    r"(overflow|use.after.free|oob|bounds|sanitiz|"
    r"auth|deserial|injection|traversal|race|"
    r"CVE-|GHSA-|memcpy|strcpy|format string)",
    re.I,
)

def prefilter(diff: Diff) -> bool:
    if diff.touches_only(TEST_DIRS | DOCS_DIRS):
        return False
    return bool(SECURITY_HINTS.search(diff.message + diff.patch))

This is deliberately dumb and high-recall. It drops ~85% of volume and costs nothing. The LLM budget is precious; we spend it only on candidates that survived a regex.

Stage 2 — The security judge

Survivors go to an LLM acting as a judge, not an author. The prompt is narrow and asks for structured output, never prose:

You are triaging a code diff for security relevance.
Do NOT speculate about impact you cannot see in the diff.
Return JSON:
{
  "is_security_fix": bool,
  "bug_class": one of [MEMORY, AUTHZ, INJECTION, DESERIAL, LOGIC, NONE],
  "sink": "<the dangerous call, or null>",
  "reachable_from_untrusted": bool | "unknown",
  "confidence": 0.0-1.0
}

Two rules make this reliable. First, we force the model to point at a concrete sink — a line it can quote. A judgment with sink: null is auto-demoted. Second, we run the judge twice with different framings and keep only agreements; disagreement routes to a human, it does not average away.

Stage 3 — Variant expansion

When the judge flags a fix, the actual value is in the unfixed siblings. We take the patched pattern, turn it into a structural query, and search the whole tree for matches that did not receive the fix:

pattern: memcpy($DST, $SRC, $LEN)
where:  $LEN is derived from network input
  and:  no bounds check dominates this call

This is where AI earns its keep. The model generalizes "this one call was wrong" into "here is the shape of the mistake," and the structural engine finds the other 40 places that shape occurs.

Stage 4 — The skeptical sandbox

The model is optimistic. Our sandbox is not. Every candidate variant gets a generated PoC hypothesis, but a hypothesis is worthless until it crashes something. We build the target, drive the input, and require an observable signal — an ASan report, a non-zero exit under a canary, a tainted value reaching the sink under a dynamic tracer. No signal, no promotion.

candidate -> generate driver -> run under ASan
  ├─ crash + security-relevant stack -> QUEUE FOR HUMAN
  └─ clean / timeout                 -> DISCARD (log for recall audit)

This single gate is why the pipeline is usable. The LLM proposes; the sandbox disposes.

The numbers that matter

Over one quarter:

  • ~3.6M commits seen, ~540K survived prefilter.
  • ~11,200 flagged by the judge as security-relevant.
  • ~1,900 produced a reproducible crash in the sandbox.
  • 31 became confirmed, reported vulnerabilities.

The headline metric we optimize is human-minutes per confirmed bug, and it keeps dropping. Precision at the human queue sits around 0.7, which is high enough that researchers trust the queue instead of second-guessing it.

Lessons

  1. Keep the LLM off the critical path for truth. It ranks and generalizes; execution decides.
  2. Force citations. A model that must quote a sink hallucinates far less than one asked "is this a vuln?"
  3. Measure recall deliberately. We replay known CVEs monthly to see how many our prefilter would have dropped. When recall dips, we loosen the regex, not the sandbox.

AI did not replace the researcher here. It replaced the part of the job that was reading 40,000 diffs so a human never has to see 39,969 of them.

#ai#variant-analysis#llm#automation#methodology
Share