mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
measure(sqli): adjudicate a 1-in-5 sample of PMPro SQL injection findings
48 findings, every 5th reviewed against source: 2 true (both privileged), 8 false; 6 of the 8 share one cause, object fields treated as attacker input when they are loaded from the database. benchmarks/push/finding_dump.py writes every finding of chosen families for review. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c82ce7fa44
commit
faf4f643df
@@ -0,0 +1,95 @@
|
||||
"""Every finding of chosen families on one checkout, whole, for a human to adjudicate.
|
||||
|
||||
The family census keeps ten examples per family, which is enough to say a family establishes something and far
|
||||
too few to say whether what it establishes is TRUE. Precision is the release gate (95%) and had never been
|
||||
measured on PHP: 224 findings on Paid Memberships Pro, none reviewed. This runs the production discovery and scan
|
||||
restricted to the families asked for, and writes every finding with its site, rung and witness, sorted by site so
|
||||
a systematic sample (every n-th) is reproducible.
|
||||
|
||||
Usage: python finding_dump.py --root <checkout> --families injection --out findings.json [--deadline 3600]
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import dataclasses
|
||||
import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from openultrasast.cpg.backend import JoernBackend
|
||||
from openultrasast.mapping import analyze_entry_points
|
||||
from openultrasast.model.contracts import ExecutionBudget
|
||||
from openultrasast.model.regions import regions_for
|
||||
from openultrasast.model.scan import ScanBudget, scan_repository
|
||||
from openultrasast.model.shipped import declared_sources
|
||||
from openultrasast.preprocess import build_file_target, enumerate_source_files
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
parser.add_argument("--families", default="injection")
|
||||
parser.add_argument("--out", type=Path, required=True)
|
||||
parser.add_argument("--deadline", type=float, default=3600.0)
|
||||
parser.add_argument("--max-regions", type=int, default=5000)
|
||||
args = parser.parse_args()
|
||||
wanted = tuple(f.strip() for f in args.families.split(",") if f.strip())
|
||||
|
||||
files = enumerate_source_files(args.root)
|
||||
opened = sum(p.stat().st_size for p in files if p.is_file())
|
||||
if not files or not opened:
|
||||
raise SystemExit(f"read nothing under {args.root}")
|
||||
targets = [build_file_target(args.root, p) for p in files]
|
||||
regions = [
|
||||
dataclasses.replace(r, families=tuple(f for f in r.families if f in wanted))
|
||||
for r in regions_for(analyze_entry_points(args.root, targets), targets, shipped=declared_sources(args.root))
|
||||
]
|
||||
regions = [r for r in regions if r.families]
|
||||
print(f"read {len(files)} files, {opened} bytes; {len(regions)} regions offer {wanted}", flush=True)
|
||||
|
||||
started = time.monotonic()
|
||||
budget = ExecutionBudget(time.monotonic() + args.deadline, 5.0)
|
||||
result = scan_repository(
|
||||
args.root,
|
||||
regions[: args.max_regions],
|
||||
backend=JoernBackend(execution_budget=budget),
|
||||
budget=ScanBudget(max_model_calls=0, max_regions=args.max_regions, order_by_evidence=True),
|
||||
execution_budget=budget,
|
||||
ranking_mode="evidence",
|
||||
unit="repository",
|
||||
population_complete=True,
|
||||
)
|
||||
outcomes = result.question_outcomes
|
||||
findings = sorted(
|
||||
(
|
||||
{
|
||||
"site": f.site,
|
||||
"family": f.family,
|
||||
"rung": str(getattr(f.rung, "value", f.rung)),
|
||||
"witness": f.witness,
|
||||
"reached_from": f.reached_from,
|
||||
}
|
||||
for f in result.findings
|
||||
if f.family in wanted
|
||||
),
|
||||
key=lambda f: f["site"],
|
||||
)
|
||||
record = {
|
||||
"root": str(args.root),
|
||||
"families": wanted,
|
||||
"seconds": round(time.monotonic() - started, 1),
|
||||
"questions": len(outcomes),
|
||||
"completed": sum(1 for o in outcomes if o.status == "completed"),
|
||||
"degradations": sorted({str(d.get("reason")) for d in result.degradations}),
|
||||
"findings": findings,
|
||||
}
|
||||
args.out.write_text(json.dumps(record, indent=2) + "\n")
|
||||
print(json.dumps({k: v for k, v in record.items() if k != "findings"} | {"findings": len(findings)}), flush=True)
|
||||
if not outcomes:
|
||||
raise SystemExit("no question was asked: the dump measured nothing")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user