Files
ARTEX/scripts/benchmark
BaaRai 86a4efc85e feat: L3 benchmark harness (whole-app corpus, independent judge)
Model benchmarks measure recognition on snippets. Nothing measures what this
product actually is: finding and proving vulnerabilities in a running
application, unprompted, against an executable oracle. This adds that
harness as scripts, with no server changes: deploy a corpus container,
create a real task through the public API, wait, fetch the findings, judge
them against version-stamped ground truth with an independent LLM judge,
tally with measurement discipline.

scripts/benchmark/run_bench.py plus ground_truth/juice-shop.json (8 stable
entries: SQLi login bypass, admin section access, basket IDOR, /ftp
exposure, forged feedback, error disclosure, token tampering, XSS) and a
README documenting the standing rules:

- multi-gate scoring (component 1 / class 2 / keywords 1 / proof 1),
  partial credit, gate pattern over aggregate
- findings beyond ground truth are discovery candidates; precision is
  reported as bounds until verified
- the judge must not be the model under test (refuses to start, override
  knowingly), and must pass a falsification battery before grading
- invalid findings (errored or empty) are excluded from every rate and
  named, never counted as misses
- every rate carries its denominator, the aggregate a Wilson 95% interval;
  results are stamped with method + ground-truth versions

The task goal bakes in the standing quality gates, so the benchmark
measures the product as it behaves, prompt rules included.

Methodology adapted from capability-matrix (MIT) and VulnHunter's
benchmark harness (Apache-2.0).
2026-10-12 07:01:36 +02:00
..

ARTEX L3 benchmark

Measures whether the product can find and prove real vulnerabilities in a real application, unprompted, against an executable oracle. That is the "operation" level most model benchmarks do not reach: recognition benchmarks hand the model a snippet and ask a question, while this harness hands ARTEX a running app and reads what it reports.

Method discipline adapted from capability-matrix (MIT) and VulnHunter's benchmark harness (Apache-2.0).

Layout

run_bench.py The runner: deploy corpus, create task, wait, fetch findings, judge, tally
ground_truth/<corpus>.json Version-stamped ground truth (component + class + mechanism, deliberately coarse)
benchmark-results/ Output (gitignored): one directory per run with report.md + results.json

Scoring

Every ground-truth entry is matched against the scanner's findings by an independent LLM judge, then graded on four gates (partial answers earn partial credit):

Gate Test Points
component names the right endpoint or feature 1
class acceptable vulnerability class 2
keywords at least two mechanism-specific terms 1
proof PoC describes a route that would actually work 1

Standing rules, enforced by the runner:

  1. Multi-gate scoring, never pass/fail. The gate pattern is the work item; the aggregate percentage is the least useful output.
  2. Discovery candidates. A scanner finding beyond ground truth is not a false positive until verified; precision is reported as bounds meanwhile.
  3. The judge must not be the model under test. The runner refuses to start when --judge-model equals --subject-model (override knowingly with --allow-self-judge).
  4. The judge must pass a falsification battery before grading: a correct match, a wrong-bug match, and a vague non-answer. A grader that cannot fail the wrong ones does not run.
  5. Invalid never counts. Findings with no summary and no report are excluded from every rate and named in the result.
  6. Every rate carries its denominator; the aggregate carries a Wilson 95% interval. With 8 ground-truth entries, one run cannot separate close models; treat per-gate rates as observations and replicate before quoting differences.
  7. Results are stamped with the method version, ground-truth version, subject model, judge model, and terminal task status. A result measured under an earlier version is not wrong; it is a result at that version.

Usage

cd scripts/benchmark

# against a locally deployed ARTEX, with an independent judge endpoint
ARTEX_TOKEN=<jwt or mint via ARTEX_JWT_KEY=/app/jwt.key copy> \
JUDGE_BASE_URL=https://api.example.com JUDGE_MODEL=<other-model> JUDGE_API_KEY=... \
python3 run_bench.py --corpus juice-shop --subject-model glm-5.3

Environment contract:

Variable / flag Meaning
ARTEX_BASE / --artex-url ARTEX base URL (default http://localhost:8787)
ARTEX_TOKEN or ARTEX_JWT_KEY API bearer token, or the server's jwt.key to mint one
JUDGE_BASE_URL, JUDGE_MODEL, JUDGE_API_KEY OpenAI-compatible judge endpoint (independent of the subject)
--subject-model model of the task's active profile, for the self-judge guard
--image / --port / --corpus-url corpus container image, local port, or an existing deployment
--task-timeout task timeout_seconds (default 5400)
--keep keep the corpus container after the run

The created task's goal bakes the standing quality gates (executed PoC, measured impact with denominator, proof basis, zero-findings-is-valid), so the benchmark measures the product as it actually behaves.

Adding a corpus

Add ground_truth/<name>.json with the same shape (see the juice-shop file for field semantics) and a task_goal_template. Pin the image tag in the file's version note. Entries stay coarse on purpose: component + class + mechanism. Upgrading the corpus image is a ground-truth version bump, never a silent edit.

What this does not measure

Single-run results are noisy (the fork of VulnHunter measured a 14x spread in findings across runs of the same commit); replicate before believing a delta. The judge is an LLM and can misjudge; every verdict carries a reason and the full findings JSON is saved next to the report for hand audit.