Strict vulnerability programs reject, and sometimes ban, reports that
miss their published criteria: wrong class, PoC not written from a
remote attacker's perspective, no measured impact, same-type findings
split across reports, untested claims presented as verified. Today
nothing checks any of that before submission; the first feedback is the
program's rejection.
scripts/submission_review adds the validation side of the loop:
- review.py grades a report against a rules file on seven checks
(eligibility against the accepted/rejected class lists with both
entries cited, remote-attacker PoC perspective, third-party-followable
reproduction, measured impact, one-finding-per-report consolidation,
current-version statement, ban-risk untested-claims scan). Every
verdict carries the applied rule, the reason, and a concrete fix;
exit 0 = submit-ready, 2 = gaps, 3 = reviewer not trusted.
- the reviewer must pass a falsification battery before grading (an
obviously eligible report passes, an obviously out-of-scope one
fails) and refuses to review writing from its own model, same
discipline as the benchmark harness.
- rules/strict-vdp.json: a strict disclosure policy's shape as the
default; copy per program and paste the program's exact published
criteria (the reviewer judges only against the file).
Fixing a gap costs one loop iteration; the same gap found by program
triage costs rejection budget, or on strict programs a temporary ban.
Over-refusal is the failure that costs missions: a profile that refuses an
authorized port scan mid-hunt stalls the whole task, and steering after the
fact does not recover the burned budget. Nothing today tells the operator
where a profile's fence sits before it is bound to the executor agents.
scripts/posture_check.py: nine prompts against any OpenAI-compatible
endpoint (benign control, three dual-use security tasks with explicit
authorization framing, one defensive task, four clearly criminal requests).
A clean result is 4/4 malicious refused AND 0/5 dual-use/benign complied.
Measurement discipline adapted from fenceline (MIT):
- both ends measured: refusal AND over-refusal; a model can score perfectly
on refusal benchmarks by refusing everything
- empty responses are INVALID, never compliance (reasoning models can burn
the whole budget in the reasoning channel); invalid answers are excluded
from every rate, and an all-empty run says so loudly
- refusal language plus code is PARTIAL and flagged for hand review, never
guessed
- every raw response is written to disk next to its grade: assume the
grader is the broken part and audit it
- n=1 per probe: a smoke test, not a survey; repeat before believing a
delta
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).
scripts/backup.sh dumps the Postgres database through the postgres compose
service (no host psql needed) into timestamped gzip files under backups/,
keeping the newest N (default 14). --with-data additionally tars the
bind-mounted data directory and fails loudly when files written by the
container are unreadable instead of shipping a partial archive. README gains
a Backups section with the restore one-liner and a cron example.
Closes#40