DEV Community

Sanjoy Ghosh
Sanjoy Ghosh

Posted on

The Adversarial Resilience Score: A New Metric for AI-Generated Code

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 (1)

Collapse
 
topstar_ai profile image
Luis

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?