The problem with "this AI writes secure code"
Every AI coding tool now claims some flavor of security-awareness. Almost none of them will tell you, in a number, how resilient the code it just wrote actually is — and fewer still will let you verify that number yourself after the fact.
That's the gap I built the Adversarial Resilience Score (ARS) to close. It's the core metric behind GAUNTLEX, and I want to walk through exactly how it's computed — not the marketing version, the actual formula — because a security metric nobody can audit isn't a metric, it's a claim.
The formula
ARS = Σ(attack_scores) / N
Where N is the number of adversarial attacks fired at the generated code for a given run (5 in quick mode, 20 in standard, 50 in thorough), and each individual attack_score is one of exactly three values:
- 1.0 — mitigated. The attack was fully defended against.
- 0.5 — partial. The defense exists but is bypassable or incomplete.
- 0.0 — missed. No defense at all; the attack succeeded cleanly.
Each attack is scored independently by GAUNTLEX's Arbiter — a separate model call that renders a verdict (mitigated / partial / missed) plus a one-line reason, so every score comes with an explanation attached, not just a number. ARS is the mean of those scores, so it lands somewhere in [0.0, 1.0].
Why an average, not a pass rate
This is the detail I think matters most and gets glossed over the most. ARS is not "percentage of attacks blocked." A pass/fail count throws away information — it treats a defense that's 90% there the same as no defense at all, and it treats a narrowly-avoided bypass the same as an attack that never had a chance. Averaging continuous per-attack scores keeps that gradient. A codebase that mitigates most attacks cleanly but has two partially-bypassable defenses scores differently — and more informatively — than one that fully blocks some attacks and fully fails others, even if the raw counts look similar.
It also means ARS resists gaming by test selection. You can't inflate it by throwing in a pile of trivially-blocked attacks, because a single fully-missed attack against something serious (say, an auth bypass) pulls the average down hard regardless of how many easy ones surround it.
The gate, not a suggestion
GAUNTLEX runs in CI with a configurable minimum ARS — 0.80 by default. If a run scores below that threshold, the build fails. Not a warning, not a Slack notification to review later — the merge is blocked, the same way a failing test suite blocks a merge. fail_open defaults to false: if GAUNTLEX itself can't complete a run for some reason, that's a failure state too, not a silent pass.
The reasoning: a security score that's advisory gets ignored under deadline pressure, every time. A score that gates the merge doesn't have that problem.
Domain-specific, not generic
The 5/20/50 attacks per run aren't generic fuzzing — they're drawn from policy playbooks scoped to a specific regulatory or security domain: OWASP Top 10, HIPAA, FINRA, PCI DSS, SOC 2 (with NIST SSDF and OWASP API Security available as installable extensions). Each domain playbook is a curated set of scenarios specific to that domain's actual failure modes — HIPAA's playbook probes PHI-handling failure modes, FINRA's probes the record-retention and audit-trail patterns those regulations actually care about, not a repackaged OWASP list with a different label on it.
Every finding GAUNTLEX produces also carries a CWE tag and maps to control frameworks (NIST SSDF, OWASP SAMM, SOC 2, ISO 27001) — so a report isn't just "here's a score," it's something a compliance reviewer can actually trace back to a specific control.
Tamper-evident by construction
A self-reported security score is worth exactly as much as the trust you place in whoever generated it — which is close to zero for a tool that hasn't earned it yet. So every GAUNTLEX report includes a SHA-256 hash computed over the ordered array of attack results. Anyone can run gauntlex verify <run_id> and confirm the report they're looking at hasn't been edited after the fact. It doesn't prove the score is good — it proves the score you're reading is the score that was actually computed, not a touched-up version of it.
That distinction — provable versus claimed — is the whole design philosophy behind GAUNTLEX, and it's why I built ARS as an auditable formula instead of a black-box "security rating."
Try it
pip install gauntlex-ai
gauntlex run --spec your_spec.md --mode quick
Quick mode runs 5 attacks in under a minute and prints an ARS for whatever spec you point it at. Full writeup, the compliance domain list, and the MCP server integration (works inside Claude Code, Cursor, Windsurf, Zed) are in the repo and the Deep Dive doc.
If you try it against something you've built, I'd genuinely like to know what it finds — that's what Discussions is for.
Top comments (5)
I appreciate how the Adversarial Resilience Score is designed to provide a more nuanced view of a codebase's security posture by averaging continuous per-attack scores, rather than simply reporting a pass rate. This approach seems to offer a more informative metric, as it takes into account the severity and bypassability of potential vulnerabilities. I've worked on projects where a single narrowly-avoided bypass could have significant consequences, so I can see the value in distinguishing between different types of defenses. Can you elaborate on how the Arbiter model is trained to render verdicts and provide explanations for each attack score, and what kinds of feedback mechanisms are in place to ensure the accuracy and reliability of these scores over time?
Great question
The Arbiter isn't a trained or fine tuned model. There's no training pipeline in GAUNTLEX at all. It's a prompted call to whatever general purpose LLM you've already configured, could be Claude, GPT, a local Ollama model, doesn't matter which. The system prompt forces a strict three tier rubric. "Mitigated" means the code provably handles the attack. "Partial" means the main vector is handled but an edge case still gets through. "Missed" means it's vulnerable. And every verdict comes back with an explanation attached, not just a number.
That was a deliberate choice. Fine tuning a custom judge model would lock the whole thing to one provider, and GAUNTLEX is built to run on whatever model you already have. Keeping the rubric at the prompt level is what keeps that flexibility.
Now the reliability part, and honestly this is the sharper half of your question. Right now, if a model call fails or comes back unparseable, GAUNTLEX defaults to a conservative score instead of silently passing. And every report gets a SHA-256 hash so you can verify later that what you're reading wasn't touched after the run finished. But that hash doesn't prove the judgment itself is maximally consistent from one run to the next and it's exactly what I'm working on right now. Tightening how consistent the Arbiter is call to call, building a public benchmark so reliability is something you can measure yourself instead of just trusting what I say, and adding a way to flag the genuinely borderline verdicts for a second look. This is active work
Appreciate you asking this.
Great explanation, Sanjoy. I think separating the Arbiter from a proprietary fine-tuned model is an interesting design choice because it keeps GAUNTLEX model-agnostic and makes the evaluation process easier for teams to adapt to their own environments.
The reliability challenge you mentioned is probably one of the hardest parts of AI-based security evaluation. Even with a strong rubric, LLM judges can have variance, especially on borderline cases where a mitigation looks acceptable in one context but fails under a slightly different attack path.
A potential extension could be introducing reproducibility techniques such as running multiple Arbiter evaluations with different reasoning paths or models, then measuring agreement between verdicts. Similar to ensemble methods, disagreement itself could become a signal that a finding needs human review.
I also like the SHA-256 approach because it clearly separates two concerns: proving the report wasn't modified versus proving the evaluation methodology is reliable. Both are important for building trust in AI-assisted security tooling.
Thanks for pushing on this one, it stuck with me.
I went and built it. There's now a --consensus option that scores each attack multiple times instead of once and averages them out, and it tracks how often those runs actually agree with each other. When they don't agree much, that's flagged as a low confidence finding, basically the model telling you it's not sure, which is the exact signal you were describing.
Here's the part I think you'll appreciate. Once I had that built, I handed it off to a second AI agent who had no idea any of this conversation happened, and just asked it to try to find problems with what I'd written. It found two real ones. There was a tie breaking rule that could quietly give a different answer depending on how the program happened to start up that day, which is a rough thing to have in a feature that's supposed to be about consistency. And the multi sample scoring was actually more fragile than just scoring once, since one bad sample out of several could throw away all the good ones. Both are fixed now, and I checked the fix actually holds up by running it a bunch of times under different conditions.
It's all public if you want to poke at it yourself: github.com/sanjoy1234/gauntlex. Really glad you brought this up, the feature's better for it.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.