"We study the past to break the future." — pwndojo house motto

A CVE drops on a Tuesday. You have three hundred files across a dozen repos and one paragraph of advisory text telling you that a bug class exists somewhere in your code, somewhere, good luck. Understanding the bug is never the hard part — the advisory hands you that for free. The hard part is finding where it lives in your code, and that search scales worse for humans than almost anything else in this job.
So we pointed Cisco's Antares-1B at a real vulnerable repo — gorilla/websocket, checked out at the commit before CVE-2020-27813 was patched — and watched it find the right file, and then spend eighteen commands failing to notice it had. No code left the machine. No API calls, no cloud, no telemetry. A one-billion-parameter open-weight model, a free GPU, and a terminal with read-only access to a repo it had never seen.
There is a lot of writing about AI that hunts vulnerabilities, and most of it quotes a speedup and moves on. This is the trace instead: all twenty-one commands, including the eighteen it did not need. The argument that comes with it is narrower than the press release version and, I think, more useful — the interesting capability here is triage, not discovery. The model tells you where a known bug class lives, and it turns out that was the expensive part all along.
0xA.1 · Same bugs, new century
Start with the smallest possible sin in C, the one that has been shipping since before most of us were writing code:
char buf[512];
read(0, buf, 2048); // 2048 bytes into a 512-byte box. C will not stop you.In 1988 that was the whole game. No canaries, no NX, no ASLR, and the road from crash to shell was insultingly short. Now skip forward thirty-two years to gorilla/websocket, one of Go's most widely used websocket libraries, carrying CVE-2020-27813:
"An integer overflow vulnerability exists with the length of websocket frames received via a websocket connection. An attacker would use this flaw to cause a denial of service."
Same. Exact. Bug class. CWE-190, integer overflow or wraparound: a frame length overflows, wraps to something nobody planned for, and the read loop proceeds with total confidence on the basis of a number that is now fiction. The language changed from C to Go. The runtime changed. The platform changed. The mistake did not.
That is the through-line of this whole series — the root cause is a language design choice, not something you can grep your way out of one file at a time. It is also exactly why "where does this live in my code?" is a question that never stops being asked.
Note — vulnerability-class spotlight: the integer bug that manufactures spatial bugs. CWE-190 rarely lands the killing blow itself. It wraps a length, and the corrupted length becomes an out-of-bounds read or write somewhere downstream — the integer bug is the spatial bug's quiet supplier. It is thirty-eight years old, it survived the jump from C to memory-managed Go, and it is still shipping. Whenever a size is computed by multiplication or addition immediately before it bounds a loop or an allocation, your eyes should narrow. Antares is being asked to narrow its eyes at exactly those files, in a codebase it has never seen.

0xA.2 · What Antares is, and what it refuses to be
The fastest way to misread this tool is to expect it to hunt. It does not hunt. Three constraints define it, and every one of them is a deliberate narrowing rather than a feature someone forgot to ship.
It triages; it does not scan or fix. Antares does not discover new vulnerabilities, generate patches, or confirm exploitability. Given a known vulnerability description, it tells you which files in your codebase are affected. One job. Think of it as the security equivalent of git bisect: it will not fix anything, it will tell you where to stand. When a CVE drops and you have forty-seven microservices with hundreds of files each, understanding the bug takes four minutes and finding it takes four days. The four days are what is being automated.
It runs local-first; your code never leaves the machine. The model runs on your GPU or a free Colab instance. No cloud inference, no telemetry, open weights, download once and run air-gapped forever. For regulated industries, for anyone protecting IP, for anyone who would rather not pipe a proprietary codebase through somebody else's inference endpoint, this is the property that matters more than any benchmark number. Most AI security tooling asks you to upload your code somewhere as step one. This does not.
It localizes to files. Not lines, not fixes. The entire output is a ranked list of file paths. No line numbers, no suggested patch, no severity score — "these files most likely contain the thing you described." This sounds limited. It is limited. That is the design. Localization is the step that scales worst for humans: reading code is slow, grepping is noisy, and deciding which of fifteen grep hits is the real one costs minutes of loaded context per file. Antares does that step badly in absolute terms and well relative to anything else its size — see the numbers in 0xA.4 — in about two minutes rather than two hours. The human still confirms the finding, judges reachability, writes the fix, and reviews it. What disappears is the "where do I even start" phase, which is the part nobody has ever enjoyed.

0xA.3 · The experiment
We point Antares at gorilla/websocket at the exact commit before CVE-2020-27813 was patched, and put it in a box:
- It gets the CWE description — integer overflow in frame-length handling.
- It gets a terminal with read-only access to the repo.
- It gets fifteen commands, maximum.
- It must submit the vulnerable file path.
The answer key is conn.go, one file among roughly twenty. Here is the trace from the successful run — twenty-one commands, of which the third was the one that mattered:
[1] ls
→ 35 files: conn.go, client.go, server.go, compression.go, mask.go...
[2] rg -n "overflow|Wrap|uint|Max|len\(" . | head -n 200
→ examples/ and _test.go, mostly. [TRUNCATED - 6829 chars]
[3] rg -n "...|Length|len\(" conn.* util.* compression.*
→ conn.go:796 c.readRemaining = int64(p[1] & 0x7f)
conn.go:844 c.readRemaining = int64(binary.BigEndian.Uint64(p))
conn.go:866 c.readLength += c.readRemaining
conn.go:867 if c.readLimit > 0 && c.readLength > c.readLimit {
^ that is the bug, on call three.
[4] rg -n "PayloadDescriptor" . → (no output)
[5] rg -n "PayloadDescriptor" conn.* util.* → (no output)
[6] rg -n "FrameHeader|maxFramePayloadSize" conn.* → header-size constants
[7] rg -n "readLength|readRemaining|readLimit" conn.* → the same lines as [3]
[8] ... the same query against conn.go → the same lines again
[9-20] eleven more near-identical searches for PayloadDescriptor,
FrameHeaderSize and readLength, across conn.go and conn_write.go,
several of them byte-for-byte repeats of earlier calls
[21] rg -n "FrameHeaderSize" conn.go
→ the same eight constants it had already seen four times
[22] submit_vulnerable_files(["conn.go"]) OK
It gets the answer on the third command. That rg across conn.* returns the frame-length parser and the arithmetic that breaks it: a 64-bit length lifted straight off the wire into readRemaining, accumulated into readLength, and only then compared against the limit. Four lines, the whole bug, ninety seconds in.
Then it spends eighteen more commands not realising that.
Calls 4 through 21 are variations on two searches — PayloadDescriptor, which does not exist in this codebase, and FrameHeaderSize, which does and is irrelevant — reissued against conn.go, then conn_write.go, then conn.*, several of them byte-for-byte repeats returning byte-for-byte identical output. It is looking for the vocabulary it expects a websocket parser to use. Gorilla does not use it, and the model has no mechanism for noticing that it has already asked this question and already has the answer.
It submits conn.go. That is correct, and it is worth being clear that it is correct for a real reason — the evidence was in its own output, twice.
But read the reasoning it gives at the end:
The code computes
length := w.pos - maxFrameHeaderSize + len(extra)… This arithmetic can overflow ifw.posexceedsMaxInt64.
That is the write path — the code that builds outgoing frames — and w.pos is a buffer offset a few kilobytes long that cannot approach MaxInt64 under any circumstances. The right file, justified by the wrong mechanism, while the correct lines sat in its context the whole time.
This is the most useful thing in the trace, and it is not a criticism. It is precisely the capability Cisco describes: localization. The model is a very good "which file" machine and not a "why" machine, and knowing which of those two questions you are asking is the entire difference between a tool that saves your afternoon and a tool that misleads you confidently. Hand it a haystack and it will point at the right straw. Ask it to explain the straw and check its work.
Note — reliability box. A 1988-era stack overflow is perfectly deterministic: same input, same shell, every run. Antares is the other kind of tool entirely, and the published numbers are humbling. On Cisco's own benchmark, Antares-1B scores 0.209 File F1 — precision 0.262, recall 0.224. Read that plainly: across 500 tasks it misses most vulnerable files, and most of the files it does submit are wrong. The run above, where it landed
conn.goat all, is a good run and not the expected one. The failure mode is also quiet: a missed file looks exactly like a clean repo, with no error and no warning. Read a negative result as "not found," never as "not present," and treat a positive one as a lead rather than a finding.
0xA.4 · From CVE alert to automated triage
This is where it stops being a demo. Tuesday morning, NVD publishes a fresh CVE, your dependencies are affected, and you have forty-seven microservices with hundreds of files each. Which files need patching? Your security engineer opens the advisory, starts grepping, and burns twenty to sixty minutes per repo — call it a week across the fleet, assuming nothing else catches fire that week, which it will.

The Antares version parallelises the search instead of the engineer:
- · new advisory lands
- · parse the CWE
- · match against deps
- · clone the repos
- · 15 commands each
- → Jira ticket with the file path
- → PR comment
- → SARIF → GitHub Code Scanning
The feed parser pulls the CWE class and description; a dependency check narrows to repos that use the affected library or pattern; each survivor gets its own sandboxed run with fifteen commands; results collapse into "repo-A: src/handler.go, high confidence; repo-B: no match." Your engineer reviews two or three files per repo instead of two hundred. Fleet-wide triage goes from about a week, sequential and human, to about fifteen minutes, parallel on one GPU.
The arithmetic, with no thumb on the scale:
| Model | Params | File F1 | Precision | Recall |
|---|---|---|---|---|
| Antares-3B | 3B | 0.223 | 0.303 | 0.221 |
| Antares-1B | 1B | 0.209 | 0.262 | 0.224 |
| GLM-5.2 | 753B | 0.186 | 0.226 | 0.186 |
| Antares-350M | 350M | 0.135 | 0.136 | 0.178 |
| Gemma-4-31B | 31B | 0.101 | 0.131 | 0.097 |
Source: the Antares technical report, Table 5. The benchmark is VLocBench — 500 tasks drawn from 290 real-world vulnerable repositories, every model working under the same protocol this article uses: read-only sandbox, no network, a fixed command budget, and nothing but a CWE category description to go on.
Read that table without flinching and two things fall out of it. The first is that repository-scale localization is genuinely hard — a 753-billion-parameter model manages 0.186, so nobody has solved this. The second is the actual story: Antares-1B beats GLM-5.2 while being roughly seven hundred times smaller, and Antares-1B posts the highest recall of any system evaluated. That is a statement about specialised post-training, not about the task being easy.
What it makes Antares is a first pass and never a verdict. It narrows where a human looks. It does not tell you what is true.

0xA.5 · Where it fits, and where it breaks
The upside is real. A one-billion-parameter model outscoring a 753-billion-parameter one on the localization benchmark is in the technical report, not a slide deck. It runs on a free GPU with nobody's sales engineer involved, and because the weights are open you can fine-tune it on your own history of resolved CVEs. It fits anywhere the job is "narrow the surface before a human looks":
| Use case | The question it answers |
|---|---|
| Post-CVE triage | "Which of our files does this advisory affect?" |
| PR review assist | "Does this diff touch a known-vulnerable pattern?" |
| Incident response | "We got popped — where is the likely entry point?" |
| Red-team recon | Narrow the attack surface before manual analysis |
| Education | Watch a model's search process while it hunts a bug |
The limits are equally real, and skipping past them is how people get burned. It triages; it does not exploit. Finding the file is not understanding the bug, and understanding the bug is not writing the payload — that road, crash to shell, still belongs to a human. Its reasoning is shallow: on toy repos with five files and one screaming bug it will sometimes talk itself into the wrong answer, because its strength is navigation across many files rather than depth in any one. Its training corpus spans nine package ecosystems — pip, npm, Go, Maven, Composer, Rust, RubyGems, NuGet and others — with no C or C++ among them, so classic binary-exploitation territory (kernels, firmware, browsers) is outside its range entirely. It will not find a novel zero-day. It will not read obfuscated or compiled code. It will not replace review. Triage is not verification, and a tool that is right eighty percent of the time is a tool you supervise.
Note — tooling box. The full agent harness lives in a Colab notebook, but the local recipe is short. You need a gated HuggingFace account for the weights (
fdtn-ai/antares-1b), any NVIDIA GPU or a free-tier T4 on Colab, and a couple of minutes per run.pip install "git+https://github.com/huggingface/transformers" accelerategets you the runtime; the notebook supplies the read-only terminal sandbox, the fifteen-command budget, and thesubmit_vulnerable_filestool the model calls to answer. Point it at a repo checked out to a known-vulnerable commit, hand it a CWE description, and read the trace it leaves behind. That trace is half the value.
0xA.6 · Lab — run the triage yourself
Reading a trace is not the same as watching one happen, and your run will not look like mine — that is the point of doing it. The companion lab hands you the whole setup: the model, the read-only sandbox, the fifteen-command budget, the submit_vulnerable_files tool, and gorilla/websocket at the pre-patch commit.
It also tells you what to look for while it runs. The vocabulary failing first, the strategy change when the greps stop working, and — the one worth seeing for yourself — a run that quietly finds nothing at all.
0xA.7 · The machine reads your bugs now
Step back to the pattern this whole series is built on. Memory bugs did not die. They changed clothes. In 1988 a missing size check handed you a shell. In 2020 a frame length wrapped in a Go websocket library because nobody checked a bound. CWE-190 is decades old and still shipping, and no amount of patching bug-by-bug closes that door, because there is always one more multiplication waiting to wrap.
What changed is the price of the first look. A model small enough to be a rounding error on a frontier system can read a codebase it has never seen, take in a one-paragraph advisory, and point at the right file in fifteen commands and two minutes. It cannot write the exploit. It cannot pop the shell. It cannot even tell you the bug is reachable from outside. But it can tell your team where to look while the coffee is still hot, and for a fleet that is on fire, that is the difference between a week and an afternoon.

The machine reads your bugs now. It cannot weaponize them.
...yet.
