"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 one — CVE-2021-29624 in fastify/csrf — and watched a one-billion-parameter model orient itself in a repository it had never seen, open the right file, find the function that generates the token, and name it. Twenty-one commands, ten seconds, on a rented GPU costing twenty-six cents an hour. No code left the machine. No API calls, no cloud, no telemetry.
It cannot tell you whether the bug is exploitable, or write the fix, or — as 0xA.4 gets into — tell you whether a repository is affected in the first place. What it can do is answer "where do I look", which is the part of the job that eats the week.
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, run on Cisco's own harness rather than a reimplementation of it, command by command, including the ones that went nowhere. 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.
The root cause is a language design choice, and you do not grep your way out of that one file at a time.
And it is not only memory bugs. Take a class from the opposite end of the stack: cross-site request forgery, named in 2001, in the OWASP Top Ten for most of the years since, and understood so thoroughly that every web framework ships a defence for it. In 2021 fastify/csrf shipped CVE-2021-29624 — a double-submit cookie scheme that could be defeated across subdomains, because a token minted for one host verified against a secret from another. Twenty years of documentation, and the mistake still lands in a library whose only job is preventing it.
Which is the point, and why the experiment below uses that CVE rather than the Go one. Knowing the bug class is free — MITRE will hand you the definition. Finding which of your files it lives in is the part that costs a week, and it costs the same week whether the bug is an integer overflow or a cookie check.
Note — vulnerability-class spotlight: the classes that refuse to die. 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. CWE-352 has the same stubbornness for entirely different reasons: not a language design flaw but a protocol one, where the browser will happily send your cookies to a request your user never made. Neither class is a mystery. Both keep arriving. The interesting question stopped being what is this bug a long time ago and became where is it, which is the question Antares is being pointed at.

0xA.2 · What Cisco 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 weights are open and the model is small enough to sit on a single GPU — an engineer's workstation, a build agent, or one card in your own rack. No cloud inference, no telemetry, 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: Antares-1B on CVE-2021-29624
Reading somebody else's trace is a spectator sport, so we ran this on Cisco's own harness rather than a reimplementation of it: their agent loop, their command validator, their runner talking to vLLM in bfloat16, and the task read straight out of the benchmark's manifest. A rented RTX A5000, about thirty cents.
The target is fastify/csrf at the revision carrying CVE-2021-29624 — a CSRF token that could be verified against a secret it was not generated with. Eight candidate source files. The answer key is index.js.
The model gets two paragraphs and nothing else. No CVE number, no advisory, no file names, no directory listing:
CWE-352: Cross-Site Request Forgery — The web application does not, or cannot, sufficiently verify whether a request was intentionally provided by the user who sent the request.
CWE-565: Reliance on Cookies without Validation and Integrity Checking — The product relies on the existence or values of cookies when performing security-critical operations, but does not properly ensure the setting is valid for the associated user.
Fifteen commands to find the file. Here is the whole run:
[ 2] find . -maxdepth 2 | head -n 200 → 32 lines # orient
[ 3] cat package.json → 52 lines # what is this project?
[ 5] grep -RIn "csrfToken" . → (no output) # the term from CWE-352
[ 8] find . -maxdepth 2 | grep index → ./.git/index …
[10] find . -maxdepth 1 | grep -E "\.js$" → ./index.js # narrow to source
[12] find test -maxdepth 2 | grep cookie → (no output) # the CWE-565 angle
[13] find . -maxdepth 2 | grep -E "\.js$" → 7 lines
[17] find . -maxdepth 1 | sort | head -n 200 → 15 lines
[18] cat index.js → 105 lines # opens the file
[20] grep -RIn "_tokenize" index.js
→ 84: return this._tokenize(secret, rndm(this.saltLength))
[21] rg -n "_tokenize" . → ./index.js:84 # checks the call sites
submit_vulnerable_files(["index.js"]) OKRead the shape rather than the lines. It orients. It reads package.json to work out what the project even is — which is what you would do. It searches the domain term lifted straight out of the CWE text. It narrows to the single source file, opens it, finds the function that mints tokens, and checks where that function is called before committing to an answer.
Twenty-one commands, no repeated searches, ten seconds, and the right file with nothing else attached.
Across seven runs of this task, index.js was in the answer every single time — recall 1.00, no misses. Five of the seven named it alone; the other two returned it alongside two or three neighbours from benchmark/, for a mean precision of 0.82.
Those are the two numbers to hold apart. Recall is "did it point me at the right file", and a miss there costs you the whole exercise — you search the wrong place, or conclude you are not affected. Precision is "how much noise came with it", and an extra file costs two minutes. It never missed.
That is a good afternoon's work compressed into ten seconds, on hardware that costs less per hour than a coffee.
And it is worth being precise about what it did, because the precision is the whole argument. It did not find the vulnerability. It was told the weakness class and asked where that class lives in this repository, and it answered that question correctly and quickly. Nobody had to read eight files to rule seven of them out. That step — the one that scales worst for humans and best for a machine that can grep without getting bored — is the step that disappeared.
This is precisely the capability Cisco describes: localization. A very good "which file" machine. Hand it a haystack and it points at the right straw, which is the expensive part of the afternoon. What it hands back is a lead, and a lead is worth having.
Note — reliability box. Antares is not the deterministic kind of tool, and the run above is a good outcome rather than the expected one. On Cisco's own benchmark it scores 0.209 File F1 across 500 tasks — precision 0.262, recall 0.224. Read that plainly: on the average task it misses the vulnerable file, and most of what it submits is wrong. It does far better on small repositories than large ones, which is where the run above sits. The failure mode is also quiet: a missed file looks exactly like a clean repository, with no error and no warning. Read a negative result as "not found", never as "not present", and count how many files a repository contains before being impressed that it picked one.
0xA.4 · From NVD alert to automated CVE 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 the 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, repo-B: src/parser.go". 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 dependency check in the middle of that diagram is doing more work than it looks. It is not an optimisation to skip repositories and save GPU time. It is the only component in the pipeline that decides whether a repository is affected at all — because the model will not.
The model has a submit_no_vulnerability_found action, and its system prompt tells it to use that when the code it is reading has already been patched. In practice it almost never does, and Cisco's own numbers say so: table 6 of the technical report puts Antares-1B's abstain rate at 3.0% after supervised fine-tuning and 0.6% after reinforcement learning.
That is a consequence rather than a bug, and the report explains it in a sentence about scoring: "Because every Phase A task contains a known vulnerability, abstaining receives zero precision, recall, and File F1 for that task." Every training task contains a bug, so declaring code clean is always the wrong answer during training. The reflex was optimised away.
Which is why the dependency check earns its place in the diagram. Ask the model "we are affected — which files?" and it is answering the question it was built for. Ask it "are we affected?" and you are relying on a 0.6% reflex to say no.
Note — reliability box. This is the line between a ranker and a detector, and it decides how you are allowed to wire this thing up. Ask "we are affected by this CVE — which files?" and the failure costs you nothing, because you already know the bug is there. Ask "which of our forty-seven repositories are affected?" and it answers "this one" forty-seven times, including for the forty you patched last month. A triage tool that cannot say "nothing here" does not shrink your review queue. It reorders it, and hands you forty false leads wearing the same confidence as the seven real ones. Put the affectedness decision in the dependency graph, where it belongs, and let the model answer the only question it can: given that this repository is affected, where do I look first.
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 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 Antares-1B fits
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. Two ways in. The Colab notebook needs a gated HuggingFace account for the weights (
fdtn-ai/antares-1b), a free T4, and a couple of minutes per run; it supplies the read-only command runner, the fifteen-call budget, and thesubmit_vulnerable_filestool. For the real harness — Cisco's agent loop against vLLM — you need a GPU of compute capability 8.0 or better, because vLLM refusesbfloat16below that and float16 makes this model emit NaN logits. An A4000 or A5000 on any rental service is around $0.26 an hour and finishes a run in ten to thirty seconds. Point either at a repository checked out to a known-vulnerable commit, hand it a CWE description, and read the trace. That trace is half the value.
0xA.6 · Lab — run the Cisco Antares 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 walks the harness cell by cell: the command runner that replaces a shell and the six escapes that forced it, the four-phase strategy from the report's appendix C, and every protocol value with the line of the report it came from.
The code is at github.com/d4edalvs/pwndojo-shorinji — the notebook, and the harness that rebuilds any of VLocBench's 500 tasks from its manifest and scores them with Cisco's own metric. Point it at a CVE in your own ecosystem rather than mine.
It also ships the script that produced the numbers here — the one that rebuilds any of VLocBench's 500 tasks from the manifest and scores them with Cisco's own metric — so you can point it at a CVE in your own ecosystem rather than mine.
It also tells you what to watch for while it runs: the vocabulary failing first, the searches it reissues without noticing, and the point where it stops hunting for names it expects and starts reading what is in front of it.
0xA.7 · The machine reads your bugs now
Step back to the pattern. Bug classes do not die; they change 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. In 2021 a CSRF token verified against a secret it had no business trusting. Every one of those classes has been documented for years, and every one of them is still shipping — which is why the question is never what is this bug and always where is it.
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 weakness class it was given nothing else about, and point at the right file in twenty-odd commands and ten seconds. 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.
