AI Can Find Vulnerabilities, But Who Verifies Them? A Practical Guide to AI Security Scanning

AI Can Find Vulnerabilities, But Who Verifies Them? A Practical Guide to AI Security Scanning
By Neel Shah · October 15, 2026
On October 8, Anthropic launched OSS Scanner, an opt-in service that gives eligible open-source projects periodic, AI-powered security scans for free. The detail that matters for the rest of us is in the fine print: its reports are fully model-generated, without human review or triage. Anthropic is upfront that this means some findings will be incorrect or invalid — and that human validation is the bottleneck they're still working against.
The scale makes the point. Over six months, their models surfaced more than 29,000 candidate vulnerabilities, but they could only manually review and triage about 6,000 of them. That ratio is the whole story of AI security scanning in 2026: discovery has become cheap and fast; confirmation has not.
So this is a post about the gap between finding a potential vulnerability and proving it's real. I'll walk through a repeatable workflow for validating an AI-generated finding, with worked examples on a small Node.js app, and finish with how to prioritize and disclose what you confirm.
How AI scanning differs from conventional static analysis
Traditional static analysis (SAST) — ESLint security plugins, Semgrep, CodeQL — matches your code against patterns and data-flow rules. It's fast, deterministic, and explainable: a finding points at a rule and a taint path. Its weakness is that it doesn't understand your application. It flags child_process.exec(userInput) whether or not that input is actually reachable by an attacker, which is why SAST is notorious for false positives.
AI-assisted scanning is different in kind. A model reads the code the way a reviewer would — following intent across files, reasoning about a project's threat model, and in the strongest cases, chaining several weaknesses into a working exploit. Anthropic reported chaining multiple findings into unauthenticated remote-code-execution exploits during testing. That's something pattern-matching simply can't do.
But that power cuts both ways:
- SAST false positives are usually mechanical — a tainted path that can't actually be triggered. They're tedious but easy to dismiss once you trace the flow.
- AI false positives are plausible. The model writes a fluent, confident explanation of a vulnerability that reads exactly like a real one — the same fluency whether it's right or wrong. It may also misread your threat model, or inflate severity.
Anthropic's own validation is a fair benchmark for what to expect. Expert pen-testers reviewed 97 critical/high findings across 48 projects: 85 (88%) met the bar for disclosure, 11 were real but duplicates of known or other findings, and only one was a true false positive. That's a genuinely strong signal rate — but notice that even at 88%, roughly one in eight "critical" findings needed a human to reclassify, and duplicates were the larger problem than outright errors. You cannot skip the human step; you can only make it efficient.
The verification workflow
Here's the triage I'd run on any AI-generated finding, in order. Each step can reject the finding before you spend time on the next.
Finding → Reproduce → De-duplicate → Rate exploitability → Verify patch → Disclose
Let's make it concrete with a small Express app that has a few deliberately planted weaknesses.
1. Reproduce it — a PoC, or it doesn't exist yet
The first and most ruthless filter: can you trigger the behavior? A finding you can't reproduce is a hypothesis, not a vulnerability.
Say the scanner flags this route for command injection:
// routes/export.js — flagged: OS command injection (high)
import { exec } from 'node:child_process';
app.get('/export', (req, res) => {
const { file } = req.query;
exec(`tar -czf /tmp/out.tgz ${file}`, (err, stdout) => {
if (err) return res.status(500).end();
res.download('/tmp/out.tgz');
});
});
Don't take the explanation's word for it — prove it with a self-contained reproducer:
# PoC: inject a second command via the `file` parameter
curl "http://localhost:3000/export?file=.%3B%20id%20%3E%20/tmp/pwned"
cat /tmp/pwned # if this file exists, the injection is real
The %3B is a URL-encoded ;. If /tmp/pwned appears, the user input reached a shell unsanitized — confirmed. A reproducer like this is the single most valuable artifact in the whole process; Anthropic noted maintainers treat a report with a working exploit attached as "basically job done," because it removes all doubt.
2. De-duplicate — is this actually new?
The biggest category of "invalid" findings in Anthropic's review wasn't errors — it was duplicates. Before you act, check:
- Is it a known CVE already tracked against a dependency? (
npm audit, the GitHub advisory DB.) - Did the scan report the same root cause in two places? One fix may close several findings.
- Is it already covered by an existing issue or a pending patch?
Treating three symptoms of one bug as three vulnerabilities wastes everyone's time and inflates your sense of risk.
3. Rate exploitability — reachable, or dead code?
A real-but-unreachable defect is low priority; a trivial one on an auth boundary is a fire. Ask:
- Is the sink reachable with attacker-controlled input? Trace the path from a request to the dangerous call. If
fileabove were hard-coded or came from a trusted config, the sameexecline is near-harmless. - What's the blast radius? RCE and auth bypass sit far above a reflected value that needs an already-authenticated admin.
- What are the preconditions? "Exploitable only by a logged-in admin on an internal network" is a very different severity than "unauthenticated, internet-facing."
This is also where you sanity-check the scanner's severity rating — Anthropic acknowledged that AI severity ratings can be inflated or miss a project's real threat model. Re-derive severity yourself (CVSS is a reasonable frame) rather than trusting the label.
4. Verify the patch — a test that fails before and passes after
If the finding came with a candidate patch (OSS Scanner often includes one), don't eyeball it — pin it with a regression test. Here's the fix for our injection:
// Fixed: no shell, arguments passed as an array
import { execFile } from 'node:child_process';
import path from 'node:path';
app.get('/export', (req, res) => {
// allowlist + no path traversal
const file = path.basename(String(req.query.file ?? ''));
execFile('tar', ['-czf', '/tmp/out.tgz', file], (err) => {
if (err) return res.status(500).end();
res.download('/tmp/out.tgz');
});
});
And the test that proves it — red before the patch, green after:
// export.test.js
import request from 'supertest';
import app from '../app.js';
test('rejects command injection in file param', async () => {
await request(app).get('/export?file=.;id > /tmp/pwned');
// the planted side effect must NOT happen
expect(fs.existsSync('/tmp/pwned')).toBe(false);
});
A patch without a test is a claim; a patch with a failing-then-passing test is a fix. This step is also your guard against the model "fixing" the symptom while leaving the root cause — the test encodes the actual attacker behavior you care about.
5. Disclose responsibly
If you confirmed a real issue in software you don't own, how you report it matters as much as finding it:
- Report privately first — a security contact,
SECURITY.md, or a GitHub private advisory — never a public issue or PR that hands attackers a roadmap. - Give maintainers the reproducer, impact, and (if you have one) a patch. That's what turns a report into a fast fix.
- Coordinate timelines. Agree on a disclosure window; don't publish details until a fix is available or the window lapses.
With exploits now developable in minutes once a bug is known, the private-first discipline isn't bureaucracy — it's the difference between a quiet fix and a live window for attackers.
So — can you trust AI security findings?
Here's my honest take. AI scanners have crossed a real threshold: on a hard academic benchmark, LLMs went from finding under 20% of vulnerabilities to over 85% in about a year, and the signal rate in practice (88% of criticals valid, in Anthropic's review) is high enough that these are no longer the "slop" maintainers used to get. Treating the output as noise would be a mistake.
But "high signal" is not "trusted." The right mental model is a tireless junior researcher who is often right, occasionally confidently wrong, and has no stake in the consequences. You wouldn't merge that person's patch unread, and you wouldn't file their report as a CVE without reproducing it. AI scanning doesn't remove the verification work — it front-loads the discovery so your scarce human attention goes entirely to confirmation.
Which is the same pattern showing up everywhere AI touches engineering: the bottleneck moved from producing the artifact to proving it's correct. For security, the proof is a reproducer, a reachability argument, and a test. Build that muscle and AI scanning becomes a force multiplier. Skip it and you're just forwarding plausible text to your maintainers.
The one-screen checklist
| Step | The question | Reject if |
|---|---|---|
| Reproduce | Can I trigger it with a PoC? | No reliable reproducer |
| De-duplicate | Is it new, and a single root cause? | Known CVE / duplicate finding |
| Exploitability | Reachable with attacker input? | Unreachable / dead code |
| Severity | Is the rating justified by the threat model? | Inflated; re-rate it |
| Patch | Does a test fail before and pass after? | Fix unproven or masks the cause |
| Disclose | Reported privately, with impact + repro? | Public before a fix exists |
Sources & further reading
- Anthropic — launching OSS Scanner, an opt-in vulnerability-finding service for open source (October 8, 2026)
- OSS Scanner overview and FAQ — Anthropic Frontier Red Team
- Google OSS-Fuzz — the fuzzing-based predecessor the model is compared to
- CyberGym vulnerability-finding benchmark
- OWASP — vulnerability disclosure & triage guidance
Neel Shah
Contract full-stack developer building e-commerce and SaaS products with Next.js, Node.js and MongoDB from Ahmedabad, India.
Work with me →Related posts
// contact
Working on something similar?
Happy to compare notes or help out — say hello.