A security auditor where the tool owns no model — the calling agent is the LLM.
The problem
Static analysis tools fall into two camps, both broken for agent-first use:
- Heavyweight SAST (Semgrep, CodeQL, Strix): powerful, but they own the whole pipeline — the rules, the triage, sometimes the LLM call that filters false positives. They need a server, a database, a Docker sandbox, or an API key. An agent that already is an LLM ends up paying for a second LLM inside the tool, or fighting the tool's opinions about what matters.
- Grep-in-a-loop: cheap, but no structure — no stable finding IDs, no persisted verdicts, no CI integration, no severity taxonomy. Every run starts from zero; the agent re-triages the same false positives forever.
The north star
The tool owns the deterministic part. The agent owns the judgment.
rules.json (the moat) --> secure binary (the engine) --> JSONL / SARIF findings
|
calling agent reads + reasons (its own LLM)
|
secure verdict <id> keep|drop --> persisted store
secure is a regex engine with stable finding IDs and a persisted verdict store. It never calls an LLM, never holds an API key, never makes a network call to a model provider. The agent driving secure (Devin, Claude Code, any coding agent) already has a model — it reads the findings, reasons with its own context, and writes back a verdict that survives across scans.
What this means in practice
| Decision | Consequence |
| No LLM client in the tool | No API key, no budget, no network call. The agent's own model is the filter. |
rules.json is data, not code | Add detections without recompiling. The rule pack is the actual product. |
Stable finding IDs (sha256(rule|file|line)) | Verdicts persist across scans, across machines, across agents. |
| SARIF 2.1.0 output | Drops into GitHub Code Scanning, VS Code, any SARIF consumer. CI-native. |
--diff / --diff-base | Scans only changed files — 0.23s on a 10k-file repo with 8 changed files. The actual fix for CI latency. |
| No Docker, no browser, no sandbox | Read-only static analysis. Never executes target code, never mutates the repo. |
What it is NOT (on purpose)
- Not an agent loop. No tool-calling LLM, no multi-step reasoning inside the tool. The agent that runs
secure does the reasoning; secure does the scanning.
- Not a report generator.
--hart prints guidance for the agent to author and publish its own HTML report with its own model. The tool doesn't bake prose-generation judgment into a binary that otherwise owns none.
- Not an index or database. The filesystem is scanned fresh every time — freshness over sophistication. The one piece of state that persists is the verdict store, and only because it's the agent's memory, not a cache the tool invents.
- Not AST-based (yet). Regex over lines is v0 — deterministic, fast, and good enough for the patterns that matter most. Tree-sitter precision is a possible v2, but it breaks the single-binary KISS model and the agent-verdict filter already handles false positives. KISS first; complexity only when it earns its keep.
The moat
The scanner is ~500 lines of MFL. rules.json is the actual product. The detection quality lives in the rule pack — the patterns, the CWE tags, the severity calibration, the language coverage. The engine is interchangeable; the rules are the accumulated knowledge. This is why the rules are plain data read fresh from disk every run: the moat must be editable without recompiling the engine.
Roadmap (only build what earns its keep)
- ✅ v0 — deterministic regex engine: JSONL findings, stable IDs, exit codes.
- ✅ v1 — agent-managed verdicts:
secure verdict keep|drop, persisted store, --show-all.
- ✅ v2 — performance:
--diff / --diff-base for CI, --workers N for parallel full scans.
- ✅ v3 — CI integration: SARIF 2.1.0 output, reusable GitHub Action,
security-events: write.
- ✅ v4 — rule pack expansion: 84 CWE-tagged detections across 16 languages.
- ✅ v5 — IaC + frameworks + more CWE: 119 rules across 17 languages — Terraform/Kubernetes/Docker IaC misconfigurations, Django/Flask/FastAPI/Spring framework rules, CSRF/mass-assignment/ReDoS/log-injection/weak-TLS.
- ✅ v6 — CI/CD + deps/config + auth/session: 147 rules across 24 languages — GitHub Actions pipeline security, dependency hardening, auth/session (JWT alg:none, unsalted hashes, session fixation, non-crypto random for secrets).
- ✅ v7 — API injection + crypto + info disclosure: 188 rules — GraphQL, prototype pollution, NoSQL/LDAP/SSTI, SSRF expanded, crypto misuse (ECB, hardcoded IV/salt, PKCS1v1.5), PHP injection patterns, info disclosure (debug code, hardcoded IPs, HTTP URLs, file upload).
- ✅ v8 — language deep dives + mobile + cloud: 235 rules across 25 languages — C/C++ format strings, Rust unsafe, Go unsafe/cgo, Java JNDI/SpEL/XPath, Android/iOS mobile, Rails/Laravel, AWS, Redis/ES injection, cleartext protocols, supply chain.
- ⬜ Baseline mode: snapshot current findings as accepted state; future runs surface only new findings. Pairs with
--diff — the path to usable static analysis in real codebases.
- ⬜ Community rule packs: split
rules.json into per-language or per-framework packs that can be contributed independently of the engine.
- ⬜ Tree-sitter precision (maybe): AST-based matching for fewer false positives. High effort, breaks single-binary KISS — only if the agent-verdict filter proves insufficient in practice.
Design principles
- KISS. The 15-lines-of-bash lesson: a deterministic regex engine over the filesystem finds most of what a heavyweight agent finds, with none of the infrastructure, cost, or attack surface. Complexity only when it earns its keep.
- Agent-first. Output is JSONL/SARIF, not prose. No TUI, no dashboard. The tool is shaped for an agent to pipe, parse, and act on — not for a human to stare at.
- BYOK. The tool never duplicates the LLM the operator already has. No model client, no API key, no network call to a provider. The agent is the LLM.
- Data over code. The rules are the moat; the engine is interchangeable. Edit detections without recompiling.
- Freshness over sophistication. No index, no database, no run journal. Scan the filesystem every time. The one persistent state is the agent's own verdict memory.