Why Generic AI Agents Fail at Security
Most AI security demos focus on a single repository or a curated benchmark. In reality, enterprise codebases span dozens of languages, thousands of dependencies, and millions of lines of code. A single-agent approach hits three fundamental walls:
- Context exhaustion — After an hour, the context window fills up and the model forgets bugs it found earlier.
- Single hypothesis — Agents explore one attack path at a time, missing the bigger picture.
- No persistence — A crash or timeout loses all progress.
Cloudflare's journey started with a 450-line security audit skill that ran on one repo. It worked, but a single run found only about half the bugs. The solution wasn't a better model — it was a model-agnostic orchestration layer that treats LLMs as interchangeable compute engines.

Architecture: The Two-Stage Pipeline
Cloudflare's system splits vulnerability management into two independent stages, each using a different model:
Stage 1: Vulnerability Discovery Harness (VDH)
| Agent | Role | Details |
|---|---|---|
| Recon | Maps architecture & threat vectors | 3 parallel agents write architecture.md |
| Hunt | Per-class attacks | Spawns sibling agents, uses sandboxed binaries |
| Validate | Mechanical check + adversarial disproval | Two-pass: schema check, then agent tries to disprove |
| Gapfill | Generates new tasks for uncovered areas | Enqueues fresh hunts for empty coverage cells |
| Dedup | Consolidates overlapping findings | Clusters by root cause |
| Trace | Walks dependency graph | Spawns consumer-repo tasks |
| Feedback | Learns from past runs | Rewrites prompts based on validation failures |
| Report | Renders human-readable output | No model required |
Stage 2: Vulnerability Validation System (VVS)
| Agent | Role | Details |
|---|---|---|
| Dedup | Identifies duplicates across all sources | Deterministic index + probabilistic reasoning |
| Judgment | Production reachability & risk scoring | Pulls from MCP, Jira, git, config |
| Fixer | Generates patches + runs regression tests | Requires clean fail→pass flip; never merges without human |
# Simplified example: orchestrating stages with a database backend
import sqlite3
from enum import Enum
class Stage(Enum):
RECON = "recon"
HUNT = "hunt"
VALIDATE = "validate"
class Harness:
def __init__(self, db_path: str):
self.conn = sqlite3.connect(db_path)
self.conn.execute("""
CREATE TABLE IF NOT EXISTS findings (
run_id TEXT,
repo TEXT,
stage TEXT,
data TEXT,
status TEXT DEFAULT 'pending'
)
""")
def run_stage(self, run_id: str, repo: str, stage: Stage, task: callable):
# Check if already completed
cursor = self.conn.execute(
"SELECT status FROM findings WHERE run_id=? AND repo=? AND stage=?",
(run_id, repo, stage.value)
)
row = cursor.fetchone()
if row and row[0] == 'completed':
return
result = task()
self.conn.execute(
"INSERT OR REPLACE INTO findings VALUES (?,?,?,?,?)",
(run_id, repo, stage.value, result, 'completed')
)
self.conn.commit()

Critical Lessons from Production
1. Persistence Before Parallelism
Every stage writes to a SQLite database keyed by (run_id, repo, stage). Findings are streamed and saved as they happen — a crash costs you only the task in flight. This is non-negotiable for runs that can take 14+ hours.
2. Watch for Silent API Errors
Sometimes a transient API error comes back as text in a 200 OK response instead of throwing an exception. You must explicitly classify the response text, not just trust the status code.
3. Sandboxing Needs Careful Configuration
If your harness runs inside Docker, the sandbox (used to crash binaries) needs seccomp=unconfined and apparmor=unconfined or it will silently fail to start.
4. Don't Wire in Static Analysis Early
Cloudflare plumbed Semgrep through the entire pipeline. In a month of runs, Hunters invoked it zero times. They preferred to read and run the code. The most-used tool? A central wishlist where agents request resources.
5. Deduplication is Its Own Problem
At scale, comparing findings with simple string matching fails. Cloudflare uses deterministic code to build inverted indexes over files, functions, and rare tokens, then only uses an LLM to reason over a short candidate list.
6. Forge Adversarial Verification
Every finding must include:
- A stated threat model (who is the attacker, what boundary is crossed)
- A PoC that runs against the original, untouched codebase
- A proposed patch
A separate Validator (which cannot file its own findings) tries to disprove everything. If a Hunter grades its own homework, it will confidently validate nonsense.
Explore the open-source skill that started it all.

Real-World Metrics & Limitations
For a standard ~30k LOC repository:
- Initial findings: ~100 raw candidates
- After validation: ~80 high-fidelity bugs
- Time to fix with human review: ~14 hours for the pipeline, then 5 days for critical fixes, 15-20 days for hardening
- Lifetime compression: ~65% reduction in candidates across the fleet
Limitations & Caveats
- No false-negative rate: There's no labeled set of every real bug in a codebase. The team measures effectiveness by tracking whether re-runs keep finding new bugs.
- Cost is hunt-stage heavy: Budget per repo, not per run. Use a task cap and worker pool (50-200 workers) to avoid wasting compute.
- Not for per-PR checks: Full scans take hours. Use cheaper, smaller harnesses for CI.
- Human-in-the-loop is mandatory: The Fixer never merges without a human signing off on a dry run.
Next Steps
- Start with a single-repo skill in your dev environment. Get prompts working well before building infrastructure.
- Add persistence (SQLite) before adding parallelism.
- Add a wishlist mechanism — let agents tell you what they need.
- Introduce adversarial verification with a separate model for validation.
- Only build cross-repo tracing when you have more than one repository that matters.