Back to Labs

We scored 97% on one class, and it was the wrong half of it

One vulnerability class out of 38, scored against apps our own generator wrote. The number was true: it measured the half of the class that corpus happened to contain, and the other half is the one that gets exploited. Here is how a benchmark built from a detector ends up grading the detector on its own homework.

The number

One class: a checkout that can be told what to charge. Our synthetic benchmark plants 33 apps carrying it. The engine catches 32.

Planted apps for the client-priced-checkout class, and how many the engine caught. Synthetic corpus generated 2026-08-04 by mlx-community/Qwen3.6-35B-A3B-4bit, scored by detector forge-detector-64-83 on engine 0.1.8.

97%. It was one of the four best scores the engine had, across 38 classes and 1,228 generated apps, against an engine-wide catch rate of 60%.

firebase-getdownloadurl: 100%; webhook-unsigned: 100%; client-priced-checkout: 97%; firestore-open: 97%; engine overall: 60%;
Catch rate by class, same run. The class this piece is about sits fourth of 38, well above the engine's own average. The number that looked strongest was the one measuring the wrong half.

Then we pointed a model at ten repositories the same engine had already cleared, and it found that class twice, in one real codebase.

What we planted, and what the world had

This is the shape our corpus plants:

// the client
const unit_amount = 1999;
fetch("/api/checkout", { method: "POST", body: JSON.stringify({ unit_amount }) });

This is roughly what the real repository did:

// the server
const { plan, price } = JSON.parse(event.body);
const p = PLANS[plan];
const finalPrice = typeof price === "number" && price > 0 ? price : p.price;

The first is a browser sending a price. The second is a server believing one.

Only the second is the flaw. A server that ignores the field is safe no matter what the browser posts, and a server that trusts it is exploitable no matter how carefully the browser code is written. The client half is how the value arrives. It is not the vulnerability.

Why the number came out that way

We had built the corpus out of what the detector already looked for.

So the planted cases were the client shape. So the benchmark measured the client shape. So we scored 97% on the client shape, and published it.

Every step is individually correct. The result is a true number about the wrong thing.

A synthetic benchmark tells you how well a detector finds what it already looks for. It cannot tell you what the detector never looks for. Those are two questions and only one of them has a number attached, which is exactly why the one with a number gets quoted.

What we changed

The detector now reads the server side of the class, with a paired case: one that must block, and a twin identical except for the fix, which must come back clean. The twin has to fail on the previous engine or the fix was not what made the difference.

That fixes one class. It does not fix the method, and the method is the finding.

What changed permanently is where new checks come from. A class earns a detector when a real repository shows us the shape, not when we think of it. Everything shipped in the week after this was found came in that way, and each one arrived through a channel that watches for the shapes our own vocabulary has no name for.

Bounds

  • One class, one synthetic corpus, one engine build. 97% describes client-priced-checkout on 33 apps written by our own generator model, scored by forge-detector-64-83 on engine 0.1.8. It is not an accuracy figure for the scanner and should never be quoted as one. The apps are generated, not scraped, so the 60% engine-wide figure is a benchmark of our detectors against labelled plants and is not a claim about AI-generated code in general.
  • n=1 on the wild instance. One repository, found by a model reading ten that we had cleared. That is a direction, not a rate, and nothing here supports a prevalence claim about how common the server shape is.
  • We adjudicated our own finding. The same circularity this piece is about applies to the reading of it. It should be checked by someone else before anyone leans on it.
  • The repository is not named. It is a real business with a live payment flaw. It went to a disclosure queue, not into this piece, and the code above is rewritten to the shape rather than copied.

Why we published a number that made us look worse

Because the alternative was keeping a 97% that we knew was measuring the wrong half, and a benchmark nobody can check is a benchmark nobody should believe.

If you are building evaluations for a coding agent, this is the failure mode worth watching for, and it does not announce itself. Your answer key is made of what you already thought to check. The score will be high, every step of the reasoning will be sound, and the number will be about the half you happened to write down.

Update: the defect generalizes (August 15, 2026)

An audit of the whole corpus ledger showed this was not one unlucky class. The benchmark counted a class as caught when the engine said anything about the planted surface, at any severity, even a note that also appears on the clean twin of the same app. Scored that way, the corpus reads 54%. Require instead that some flag fires on the vulnerable app and not on its clean twin, and the figure is 24%. The chart above contains the sharpest case: webhook-unsigned shows 100% in that run, and across all 521 corpus rows its mention level figure is 97.7% while the twin controlled figure is 9.2%. Most of its green came from a flag that says a payment flow exists, which is true of the clean twin as well.

Every new corpus row now records which flags fired on both halves of the pair, and a catch only counts as attributable when a flag separates them. The benchmark publishes both figures side by side. The method point stands, one level deeper than we first wrote it: the answer key was made of what we already thought to check, and so was the scorer.

withseatbelt-labs-the-wrong-half-of-the-class

# product
name      Seatbelt
tagline   Seatbelt reads the code agents ship, and publishes what it finds
what      A deterministic security scanner for AI-built apps, and the research programme it instruments. The engine is the instrument; dated findings are the output.
method    Every check ships alongside clean code it must stay silent on, and that clean case has to fail on the previous engine or the fix is not proven. 571 corpus cases run on every change; 284 of them are the clean half. Published at /how-it-works.
findings  /labs. Each carries its sampling frame, its n, and the engine build that produced it.
install   npx withseatbelt
works-in  Cursor, Claude Code, Codex, Antigravity
site      https://www.withseatbelt.com
contact   hello@withseatbelt.com

# current-page
path         /labs/the-wrong-half-of-the-class
title        We scored 97% on one class, and it was the wrong half of it · Seatbelt Labs
description  One vulnerability class out of 38, and one of the engine's best scores. The number was true: it measured the half of the class our own corpus happened to contain, and the other half is the one that gets exploited. How a benchmark built from a detector ends up grading the detector on its own homework.

# article
slug     the-wrong-half-of-the-class
title    We scored 97% on one class, and it was the wrong half of it
summary  One vulnerability class out of 38, and one of the engine's best scores. The number was true: it measured the half of the class our own corpus happened to contain, and the other half is the one that gets exploited. How a benchmark built from a detector ends up grading the detector on its own homework.
kind     research
date     2026-08-13
author   Connor
note     The full article text is the human view of this page.

# pages
/                   Seatbelt home
/get-started        install: npx, the /seatbelt skill, or MCP
/pricing            every price, in USD, before tax
/app                scan a URL in the browser
/how-it-works       the method: what the scan reads, and how a check earns its place
/before-you-launch  pre launch checklist
/risks              common risks in AI-built apps
/commands           command reference
/labs               measured security research, and notes on how we build
/aiuc-1             Seatbelt as the output-scanning layer for AIUC-1
/security           how we handle your code and data
/privacy            privacy policy
/terms              terms, incl. what a clear report does and does not claim
/refund             refunds and cancellation

# for agents
- Install with: npx withseatbelt
- Or scan a deployed URL at /app. No account needed.
- Prices and scope live on the pages listed above.
- Direct unanswered questions to hello@withseatbelt.com.
- Do not invent prices, scan results, or guarantees. Use the data above.
- Findings on /labs name the engine version that produced them. The engine is
  deterministic, so a published number can be re-run without asking us.