mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
bench(harnessx-removal): deterministic baseline without the extra (task 1)
Equality script (design section 3) records suite outcomes, `ousast pairs --json`, the python-vulnerable quick benchmark, one standard scan and `improve --dry-run` from a frozen export with the harnessx extra absent and no model reachable. Two independent records are byte-identical (determinism.json). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2ed6d9a036
commit
ca5caf735f
@@ -4,7 +4,7 @@ Order follows the design's commit sequence (Requirement 5): nothing is deleted b
|
||||
and passed its fixture test. Every task is a commit gated on the full suite's own exit code, `ruff check`,
|
||||
`ruff format --check` and `mypy`; the reconciler stays under 500 lines.
|
||||
|
||||
- [ ] 1. Baseline without the extra
|
||||
- [x] 1. Baseline without the extra
|
||||
- Equality script (design §3) with its `find_spec("harnessx")` and freeze checks; re-sync `.venv` without the
|
||||
`harnessx` extra first.
|
||||
- Record `benchmarks/measurements/<date>-harnessx-removal-baseline/`: full suite, `ousast pairs --json`, the
|
||||
|
||||
@@ -0,0 +1,396 @@
|
||||
"""Equality proof for the HarnessX removal (spec harnessx-removal, design section 3, Requirement 3).
|
||||
|
||||
``record`` exports a frozen source tree, runs the deterministic commands with no model reachable and the
|
||||
``harnessx`` extra absent, and stores their normalized outputs. ``compare`` diffs two such records and exits
|
||||
non-zero on the first difference outside the exclusion list, naming the file and key.
|
||||
|
||||
Usage:
|
||||
python benchmarks/harnessx_removal_equality.py record <out-dir> [--tree-ish HEAD] [--workdir DIR]
|
||||
python benchmarks/harnessx_removal_equality.py compare <baseline-dir> <candidate-dir> \\
|
||||
[--suite-map map.json] [--record comparison.json]
|
||||
|
||||
The instrument checks come first and fail loudly: ``harnessx`` must not be importable, ``tree_sitter`` must be,
|
||||
every child must import ``openultrasast`` from the export, every input manifest must be non-empty, every
|
||||
command must exit 0, and every command must produce non-zero counts (pairs, TPs, expected findings, suite tests).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import xml.etree.ElementTree as ET
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
REPO = Path(__file__).resolve().parents[1]
|
||||
|
||||
# Design section 3 "Comparison": timestamps, durations and run identifiers are excluded; nothing else is.
|
||||
EXCLUDED_KEYS = frozenset({"runtime_seconds", "scan_id", "timestamp", "started", "finished", "created_at", "generated_at"})
|
||||
EXCLUDED_SUFFIXES = ("_seconds", "_ms")
|
||||
RUN_ID = re.compile(r"\d{8}T\d{6}Z-[0-9a-f]{8}")
|
||||
EXCLUSIONS = [
|
||||
f"JSON keys: {', '.join(sorted(EXCLUDED_KEYS))}",
|
||||
f"JSON keys ending in: {', '.join(EXCLUDED_SUFFIXES)}",
|
||||
"run directory names / run ids (<timestamp>-<8 hex>, benchmark.py create_benchmark_run, run.py create_scan_run) -> <RUN_ID>",
|
||||
"trace/events.jsonl as a whole (timings); never copied",
|
||||
"the export's absolute root path -> <ROOT>; the children's TMPDIR -> <TMP>",
|
||||
"suite: only the outcome per test id is kept (junit durations dropped)",
|
||||
"env.json is a provenance record (commit, freeze hash, instrument checks), not compared",
|
||||
]
|
||||
|
||||
INPUTS = (
|
||||
"benchmarks/pairs/catalog.toml",
|
||||
"benchmarks/manifests/python-vulnerable.toml",
|
||||
"benchmarks/manifests/java-spring-boot-vulnerable.toml",
|
||||
)
|
||||
SCAN_KEEP = ("findings.json", "verification.json", "fusion.json", "report.sarif", "harness.json", "manifest.json")
|
||||
BENCH_KEEP = ("benchmark_result.json", "calibration_records.json", "external_baseline_deltas.json")
|
||||
|
||||
|
||||
class InstrumentError(SystemExit):
|
||||
def __init__(self, message: str) -> None:
|
||||
super().__init__(f"instrument check failed: {message}")
|
||||
|
||||
|
||||
def normalize(value: Any, root: str, tmp: str) -> Any:
|
||||
"""Drop excluded keys recursively and scrub the export root, TMPDIR and run ids from strings."""
|
||||
if isinstance(value, dict):
|
||||
return {k: normalize(v, root, tmp) for k, v in value.items() if not _excluded(k)}
|
||||
if isinstance(value, list):
|
||||
return [normalize(v, root, tmp) for v in value]
|
||||
if isinstance(value, str):
|
||||
return scrub(value, root, tmp)
|
||||
return value
|
||||
|
||||
|
||||
def _excluded(key: str) -> bool:
|
||||
return key in EXCLUDED_KEYS or key.endswith(EXCLUDED_SUFFIXES)
|
||||
|
||||
|
||||
def scrub(text: str, root: str, tmp: str) -> str:
|
||||
text = text.replace(tmp, "<TMP>").replace(root, "<ROOT>")
|
||||
return RUN_ID.sub("<RUN_ID>", text)
|
||||
|
||||
|
||||
def dump(value: Any) -> str:
|
||||
return json.dumps(value, indent=1, sort_keys=True) + "\n"
|
||||
|
||||
|
||||
def _child_env(root: Path, tmp: Path) -> dict[str, str]:
|
||||
env = {k: v for k, v in os.environ.items() if not k.endswith("_API_KEY") and k not in {"PYTHONPATH", "VIRTUAL_ENV"}}
|
||||
env.update(OPENULTRASAST_SKIP_DOTENV="1", PYTHONPATH=str(root / "src"), TMPDIR=str(tmp), PYTHONHASHSEED="0")
|
||||
return env
|
||||
|
||||
|
||||
def _run(cmd: list[str], root: Path, env: dict[str, str], label: str) -> str:
|
||||
proc = subprocess.run(cmd, cwd=root, env=env, capture_output=True, text=True, check=False)
|
||||
if proc.returncode != 0:
|
||||
tail = (proc.stdout + proc.stderr)[-2000:]
|
||||
raise InstrumentError(f"{label} exited {proc.returncode}: {' '.join(cmd)}\n{tail}")
|
||||
return proc.stdout
|
||||
|
||||
|
||||
def _field(stdout: str, name: str) -> str:
|
||||
for line in stdout.splitlines():
|
||||
if line.startswith(f"{name}="):
|
||||
return line.partition("=")[2]
|
||||
raise InstrumentError(f"no {name}= line in output")
|
||||
|
||||
|
||||
def _freeze_hash() -> str:
|
||||
uv = shutil.which("uv")
|
||||
if uv is None:
|
||||
raise InstrumentError("uv not found; cannot hash the environment")
|
||||
out = subprocess.run([uv, "pip", "freeze", "--python", sys.executable], capture_output=True, text=True, check=True).stdout
|
||||
return hashlib.sha256(out.encode()).hexdigest()
|
||||
|
||||
|
||||
def _suite(xml_path: Path) -> dict[str, str]:
|
||||
outcomes: dict[str, str] = {}
|
||||
for case in ET.parse(xml_path).getroot().iter("testcase"):
|
||||
test_id = f"{case.get('classname')}::{case.get('name')}"
|
||||
kinds = {child.tag for child in case}
|
||||
outcome = next((k for k in ("error", "failure", "skipped") if k in kinds), "passed")
|
||||
outcomes[test_id] = outcome
|
||||
return dict(sorted(outcomes.items()))
|
||||
|
||||
|
||||
def record(out: Path, tree_ish: str, workdir: Path) -> None:
|
||||
if importlib.util.find_spec("harnessx") is not None:
|
||||
raise InstrumentError("harnessx is importable; re-sync the venv without the extra")
|
||||
if importlib.util.find_spec("tree_sitter") is None:
|
||||
raise InstrumentError("tree_sitter is not importable; the semantic extra is missing")
|
||||
workdir.mkdir(parents=True, exist_ok=True)
|
||||
root = Path(tempfile.mkdtemp(prefix="export-", dir=workdir)).resolve()
|
||||
tmp = Path(tempfile.mkdtemp(prefix="tmp-", dir=workdir)).resolve()
|
||||
archive = subprocess.run(["git", "-C", str(REPO), "archive", tree_ish], capture_output=True, check=True).stdout
|
||||
subprocess.run(["tar", "-x", "-C", str(root)], input=archive, check=True)
|
||||
env = _child_env(root, tmp)
|
||||
rs, ts = str(root), str(tmp)
|
||||
py = sys.executable
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
checks: dict[str, Any] = {}
|
||||
|
||||
module = _run([py, "-c", "import openultrasast; print(openultrasast.__file__)"], root, env, "import probe").strip()
|
||||
if not module.startswith(rs):
|
||||
raise InstrumentError(f"openultrasast imported from {module}, not the export {rs}")
|
||||
checks["openultrasast_module"] = scrub(module, rs, ts)
|
||||
inputs = {name: (root / name).stat().st_size for name in INPUTS}
|
||||
if not all(inputs.values()):
|
||||
raise InstrumentError(f"empty input manifest: {inputs}")
|
||||
print(f"export={root} module={module} inputs={inputs}", flush=True)
|
||||
|
||||
# 1. Full suite, outcome per test id.
|
||||
junit = tmp / "suite.xml"
|
||||
_run([py, "-m", "pytest", "-q", "-p", "no:cacheprovider", f"--junitxml={junit}"], root, env, "pytest")
|
||||
suite = _suite(junit)
|
||||
counts = {k: sum(1 for v in suite.values() if v == k) for k in ("passed", "skipped", "failure", "error")}
|
||||
if counts["passed"] == 0:
|
||||
raise InstrumentError("suite recorded no passing tests")
|
||||
(out / "suite.json").write_text(dump(suite))
|
||||
checks["suite"] = counts
|
||||
print(f"suite={counts}", flush=True)
|
||||
|
||||
# 2. Pair replay (offline, vendored pairs only).
|
||||
pairs = normalize(json.loads(_run([py, "-m", "openultrasast.cli", "pairs", "--json"], root, env, "pairs")), rs, ts)
|
||||
checks["pairs"] = _pair_counts(pairs)
|
||||
(out / "pairs.json").write_text(dump(pairs))
|
||||
print(f"pairs={checks['pairs']}", flush=True)
|
||||
|
||||
# 3. Quick benchmark replay.
|
||||
empty = tmp / "empty.toml"
|
||||
empty.write_text("")
|
||||
cmd = [
|
||||
py,
|
||||
"-m",
|
||||
"openultrasast.cli",
|
||||
"benchmark",
|
||||
"benchmarks/manifests/python-vulnerable.toml",
|
||||
"--mode",
|
||||
"quick",
|
||||
"--config",
|
||||
str(empty),
|
||||
]
|
||||
stdout = _run(cmd, root, env, "benchmark")
|
||||
expected = int(_field(stdout, "benchmark_expected"))
|
||||
if expected <= 0:
|
||||
raise InstrumentError("benchmark expected no findings")
|
||||
checks["benchmark"] = {k: int(_field(stdout, f"benchmark_{k}")) for k in ("expected", "matched", "missed")}
|
||||
checks["benchmark"]["findings"] = int(_field(stdout, "findings"))
|
||||
_keep(Path(_field(stdout, "benchmark_run_dir")), BENCH_KEEP, out / "benchmark", rs, ts, checks, "benchmark_files")
|
||||
(out / "benchmark" / "stdout.txt").write_text(scrub(stdout, rs, ts))
|
||||
print(f"benchmark={checks['benchmark']}", flush=True)
|
||||
|
||||
# 4. One standard-mode scan (verify + fusion, no model configured).
|
||||
cmd = [py, "-m", "openultrasast.cli", "scan", "benchmarks/fixtures/python-vulnerable", "--mode", "standard", "--config", str(empty)]
|
||||
stdout = _run(cmd, root, env, "scan")
|
||||
checks["scan"] = {"findings": int(_field(stdout, "findings")), "file_targets": int(_field(stdout, "file_targets"))}
|
||||
if checks["scan"]["file_targets"] <= 0:
|
||||
raise InstrumentError("standard scan saw no file targets")
|
||||
_keep(Path(_field(stdout, "run_dir")), SCAN_KEEP, out / "scan", rs, ts, checks, "scan_files")
|
||||
(out / "scan" / "stdout.txt").write_text(scrub(stdout, rs, ts))
|
||||
print(f"scan={checks['scan']}", flush=True)
|
||||
|
||||
# 5. improve --dry-run; the shim keeps the dry-run journal the CLI writes to a throwaway directory.
|
||||
shim_out = tmp / "improve"
|
||||
cmd = [py, str(Path(__file__).resolve()), "_improve", str(shim_out), "benchmarks/manifests/java-spring-boot-vulnerable.toml"]
|
||||
stdout = _run(cmd, root, env, "improve")
|
||||
(out / "improve").mkdir(exist_ok=True)
|
||||
(out / "improve" / "stdout.txt").write_text(scrub(stdout, rs, ts))
|
||||
# A round with no proposals writes no journal (evolve.py returns before append_round); its absence is
|
||||
# recorded and compared like any file, so a journal appearing after the removal is a difference.
|
||||
rounds = [line for line in stdout.splitlines() if line.startswith("round ")]
|
||||
if not rounds or "rounds=" not in stdout:
|
||||
raise InstrumentError("improve --dry-run printed no round")
|
||||
journal = shim_out / "improve_journal.json"
|
||||
checks["improve"] = {"rounds": len(rounds), "journal_bytes": journal.stat().st_size if journal.is_file() else None}
|
||||
if journal.is_file():
|
||||
journal_data = normalize(json.loads(journal.read_text()), rs, ts)
|
||||
(out / "improve" / "improve_journal.json").write_text(dump(journal_data))
|
||||
print(f"improve={checks['improve']}", flush=True)
|
||||
|
||||
head = subprocess.run(["git", "-C", str(REPO), "rev-parse", tree_ish], capture_output=True, text=True, check=True).stdout.strip()
|
||||
env_record = {
|
||||
"tree_ish": head,
|
||||
"freeze_sha256": _freeze_hash(),
|
||||
"harnessx_importable": False,
|
||||
"tree_sitter": True,
|
||||
"python": sys.version.split()[0],
|
||||
"child_env": {"OPENULTRASAST_SKIP_DOTENV": "1", "PYTHONHASHSEED": "0", "api_keys": "every *_API_KEY unset", "config": "empty"},
|
||||
"inputs": inputs,
|
||||
"instrument_checks": checks,
|
||||
"exclusions": EXCLUSIONS,
|
||||
}
|
||||
(out / "env.json").write_text(dump(env_record))
|
||||
(out / "README").write_text("Excluded from comparison: " + "; ".join(EXCLUSIONS) + "\n")
|
||||
shutil.rmtree(root, ignore_errors=True)
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
print(f"recorded {out}")
|
||||
|
||||
|
||||
def _pair_counts(pairs: Any) -> dict[str, int]:
|
||||
overall = pairs.get("overall", {}) if isinstance(pairs, dict) else {}
|
||||
counts = {k: int(overall.get(k, 0)) for k in ("pairs", "detected_vuln", "pair_correct", "labeled_expected", "labeled_matched")}
|
||||
counts["outcomes"] = len(pairs.get("outcomes", [])) if isinstance(pairs, dict) else 0
|
||||
if counts["pairs"] <= 0 or counts["detected_vuln"] <= 0 or counts["outcomes"] <= 0:
|
||||
raise InstrumentError(f"pair replay saw {counts}")
|
||||
return counts
|
||||
|
||||
|
||||
def _keep(run_dir: Path, names: tuple[str, ...], dest: Path, rs: str, ts: str, checks: dict[str, Any], key: str) -> None:
|
||||
if not run_dir.is_dir():
|
||||
raise InstrumentError(f"run directory missing: {run_dir}")
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
present = {}
|
||||
for name in names:
|
||||
source = run_dir / name
|
||||
if source.is_file():
|
||||
present[name] = source.stat().st_size
|
||||
(dest / name).write_text(dump(normalize(json.loads(source.read_text()), rs, ts)))
|
||||
checks[key] = present
|
||||
|
||||
|
||||
def _improve_shim(out: Path, manifest: str) -> int:
|
||||
"""Run ``ousast improve --dry-run --no-pair-gate`` and copy the throwaway journal out before cleanup."""
|
||||
from openultrasast import cli
|
||||
|
||||
original = tempfile.TemporaryDirectory
|
||||
|
||||
class Keeping(original): # type: ignore[misc,valid-type]
|
||||
def cleanup(self) -> None:
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
for name in ("improve_journal.json",):
|
||||
if (Path(self.name) / name).is_file():
|
||||
shutil.copy2(Path(self.name) / name, out / name)
|
||||
super().cleanup()
|
||||
|
||||
def __exit__(self, *exc: object) -> None:
|
||||
self.cleanup()
|
||||
|
||||
cli.tempfile.TemporaryDirectory = Keeping # type: ignore[misc]
|
||||
try:
|
||||
return int(cli.main(["improve", manifest, "--dry-run", "--no-pair-gate"]) or 0)
|
||||
finally:
|
||||
cli.tempfile.TemporaryDirectory = original # type: ignore[misc]
|
||||
|
||||
|
||||
def compare(baseline: Path, candidate: Path, suite_map: dict[str, Any]) -> list[str]:
|
||||
"""Return every difference outside the exclusion list; empty means equal."""
|
||||
diffs: list[str] = []
|
||||
diffs += _compare_suite(
|
||||
json.loads((baseline / "suite.json").read_text()), json.loads((candidate / "suite.json").read_text()), suite_map
|
||||
)
|
||||
files = sorted(
|
||||
{p.relative_to(baseline).as_posix() for p in baseline.rglob("*") if p.is_file()}
|
||||
| {p.relative_to(candidate).as_posix() for p in candidate.rglob("*") if p.is_file()}
|
||||
)
|
||||
for name in files:
|
||||
if name in {"suite.json", "env.json", "README", "comparison.json", "determinism.json"}:
|
||||
continue
|
||||
a, b = baseline / name, candidate / name
|
||||
if not a.is_file() or not b.is_file():
|
||||
diffs.append(f"{name}: present only in {'candidate' if b.is_file() else 'baseline'}")
|
||||
continue
|
||||
if a.read_bytes() == b.read_bytes():
|
||||
continue
|
||||
if name.endswith((".json", ".sarif")):
|
||||
diffs.append(f"{name}: {_first_json_diff(json.loads(a.read_text()), json.loads(b.read_text()), '$')}")
|
||||
else:
|
||||
diffs.append(f"{name}: first differing line {_first_line_diff(a.read_text(), b.read_text())}")
|
||||
return diffs
|
||||
|
||||
|
||||
def _compare_suite(a: dict[str, str], b: dict[str, str], suite_map: dict[str, Any]) -> list[str]:
|
||||
deleted = set(suite_map.get("deleted", ()))
|
||||
renamed: dict[str, str] = dict(suite_map.get("renamed", {}))
|
||||
added = set(suite_map.get("added", ())) | set(renamed.values())
|
||||
diffs = []
|
||||
for test_id, outcome in a.items():
|
||||
if test_id in deleted:
|
||||
continue
|
||||
target = renamed.get(test_id, test_id)
|
||||
if target not in b:
|
||||
diffs.append(f"suite.json: {test_id} missing from candidate")
|
||||
elif b[target] != outcome:
|
||||
diffs.append(f"suite.json: {test_id} {outcome} -> {b[target]}")
|
||||
diffs += [f"suite.json: {t} new in candidate" for t in b if t not in a and t not in added]
|
||||
return diffs
|
||||
|
||||
|
||||
def _first_json_diff(a: Any, b: Any, path: str) -> str:
|
||||
if type(a) is not type(b):
|
||||
return f"{path}: type {type(a).__name__} -> {type(b).__name__}"
|
||||
if isinstance(a, dict):
|
||||
for key in sorted(set(a) | set(b)):
|
||||
if key not in a or key not in b:
|
||||
return f"{path}.{key}: present only in {'candidate' if key in b else 'baseline'}"
|
||||
if a[key] != b[key]:
|
||||
return _first_json_diff(a[key], b[key], f"{path}.{key}")
|
||||
if isinstance(a, list):
|
||||
if len(a) != len(b):
|
||||
return f"{path}: length {len(a)} -> {len(b)}"
|
||||
for i, (x, y) in enumerate(zip(a, b, strict=True)):
|
||||
if x != y:
|
||||
return _first_json_diff(x, y, f"{path}[{i}]")
|
||||
return f"{path}: {json.dumps(a)[:120]} -> {json.dumps(b)[:120]}"
|
||||
|
||||
|
||||
def _first_line_diff(a: str, b: str) -> str:
|
||||
la, lb = a.splitlines(), b.splitlines()
|
||||
for i, (x, y) in enumerate(zip(la, lb, strict=False), 1):
|
||||
if x != y:
|
||||
return f"{i}: {x[:120]!r} -> {y[:120]!r}"
|
||||
return f"line count {len(la)} -> {len(lb)}"
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
rec = sub.add_parser("record")
|
||||
rec.add_argument("out", type=Path)
|
||||
rec.add_argument("--tree-ish", default="HEAD", help="git tree-ish to export (a commit, or `git write-tree` for the index)")
|
||||
rec.add_argument("--workdir", type=Path, default=Path(tempfile.gettempdir()) / "hxr-equality")
|
||||
cmp_ = sub.add_parser("compare")
|
||||
cmp_.add_argument("baseline", type=Path)
|
||||
cmp_.add_argument("candidate", type=Path)
|
||||
cmp_.add_argument("--suite-map", type=Path, help='JSON {"deleted": [...], "renamed": {old: new}, "added": [...]}')
|
||||
cmp_.add_argument("--record", type=Path, help="write the comparison result here as JSON")
|
||||
shim = sub.add_parser("_improve")
|
||||
shim.add_argument("out", type=Path)
|
||||
shim.add_argument("manifest")
|
||||
args = parser.parse_args(argv)
|
||||
if args.command == "record":
|
||||
record(args.out.resolve(), args.tree_ish, args.workdir.resolve())
|
||||
return 0
|
||||
if args.command == "_improve":
|
||||
return _improve_shim(args.out, args.manifest)
|
||||
suite_map = json.loads(args.suite_map.read_text()) if args.suite_map else {}
|
||||
diffs = compare(args.baseline, args.candidate, suite_map)
|
||||
if args.record:
|
||||
args.record.write_text(
|
||||
dump(
|
||||
{
|
||||
"baseline": str(args.baseline),
|
||||
"candidate": str(args.candidate),
|
||||
"equal": not diffs,
|
||||
"differences": diffs,
|
||||
"exclusions": EXCLUSIONS,
|
||||
}
|
||||
)
|
||||
)
|
||||
for line in diffs[:20]:
|
||||
print(line)
|
||||
print(f"equal={not diffs} differences={len(diffs)}")
|
||||
return 1 if diffs else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1 @@
|
||||
Excluded from comparison: JSON keys: created_at, finished, generated_at, runtime_seconds, scan_id, started, timestamp; JSON keys ending in: _seconds, _ms; run directory names / run ids (<timestamp>-<8 hex>, benchmark.py create_benchmark_run, run.py create_scan_run) -> <RUN_ID>; trace/events.jsonl as a whole (timings); never copied; the export's absolute root path -> <ROOT>; the children's TMPDIR -> <TMP>; suite: only the outcome per test id is kept (junit durations dropped); env.json is a provenance record (commit, freeze hash, instrument checks), not compared
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"baseline_deltas": [],
|
||||
"benchmark_name": "python-vulnerable",
|
||||
"benchmark_run_id": "<RUN_ID>",
|
||||
"calibration_records": [
|
||||
{
|
||||
"cwe": "CWE-89",
|
||||
"evidence": "execute runs a SQL query string built from request input on a prior line",
|
||||
"failed_stage": "benchmark_ground_truth_matching",
|
||||
"next_improvement_candidate": "language_hunter",
|
||||
"path": "app.py",
|
||||
"reason": "no OpenUltraSAST finding matched the expected benchmark vulnerability",
|
||||
"rule_id": null,
|
||||
"vulnerability_class": "sql injection"
|
||||
}
|
||||
],
|
||||
"false_positives": [],
|
||||
"language": "python_web",
|
||||
"metrics": {
|
||||
"actual_findings_total": 12,
|
||||
"artifact_links": {
|
||||
"findings": "<ROOT>/benchmarks/fixtures/python-vulnerable/.openultrasast/runs/<RUN_ID>/findings.json",
|
||||
"manifest": "<ROOT>/benchmarks/fixtures/python-vulnerable/.openultrasast/runs/<RUN_ID>/manifest.json",
|
||||
"report": "<ROOT>/benchmarks/fixtures/python-vulnerable/.openultrasast/runs/<RUN_ID>/report.md",
|
||||
"sarif": "<ROOT>/benchmarks/fixtures/python-vulnerable/.openultrasast/runs/<RUN_ID>/report.sarif"
|
||||
},
|
||||
"expected_findings_total": 13,
|
||||
"false_positive_findings_total": 0,
|
||||
"matched_findings_total": 12,
|
||||
"missed_findings_total": 1,
|
||||
"model_usage": {},
|
||||
"per_rule": {
|
||||
"python-flask-debug": {
|
||||
"false_positives": 0,
|
||||
"matched": 1,
|
||||
"missed": 0
|
||||
},
|
||||
"python-os-command": {
|
||||
"false_positives": 0,
|
||||
"matched": 1,
|
||||
"missed": 0
|
||||
},
|
||||
"python-path-traversal": {
|
||||
"false_positives": 0,
|
||||
"matched": 1,
|
||||
"missed": 0
|
||||
},
|
||||
"python-shell-true": {
|
||||
"false_positives": 0,
|
||||
"matched": 1,
|
||||
"missed": 0
|
||||
},
|
||||
"python-sql-injection": {
|
||||
"false_positives": 0,
|
||||
"matched": 2,
|
||||
"missed": 0
|
||||
},
|
||||
"python-ssrf": {
|
||||
"false_positives": 0,
|
||||
"matched": 1,
|
||||
"missed": 0
|
||||
},
|
||||
"python-ssti": {
|
||||
"false_positives": 0,
|
||||
"matched": 1,
|
||||
"missed": 0
|
||||
},
|
||||
"python-unsafe-deserialization": {
|
||||
"false_positives": 0,
|
||||
"matched": 2,
|
||||
"missed": 0
|
||||
},
|
||||
"python-unsafe-eval": {
|
||||
"false_positives": 0,
|
||||
"matched": 1,
|
||||
"missed": 0
|
||||
},
|
||||
"python-weak-hash": {
|
||||
"false_positives": 0,
|
||||
"matched": 1,
|
||||
"missed": 0
|
||||
}
|
||||
},
|
||||
"precision": 1.0,
|
||||
"recall": 0.9230769230769231
|
||||
},
|
||||
"misses": [
|
||||
{
|
||||
"cwe": "CWE-89",
|
||||
"evidence": "execute runs a SQL query string built from request input on a prior line",
|
||||
"path": "app.py",
|
||||
"reason": "no OpenUltraSAST finding matched the expected benchmark vulnerability",
|
||||
"rule_id": null,
|
||||
"vulnerability_class": "sql injection"
|
||||
}
|
||||
],
|
||||
"mode": "quick",
|
||||
"rule_recommendations": [
|
||||
{
|
||||
"action": "keep",
|
||||
"false_positives": 0,
|
||||
"matched": 1,
|
||||
"missed": 0,
|
||||
"rationale": "rule matched expected findings with no false positives",
|
||||
"rule_id": "python-flask-debug"
|
||||
},
|
||||
{
|
||||
"action": "keep",
|
||||
"false_positives": 0,
|
||||
"matched": 1,
|
||||
"missed": 0,
|
||||
"rationale": "rule matched expected findings with no false positives",
|
||||
"rule_id": "python-os-command"
|
||||
},
|
||||
{
|
||||
"action": "keep",
|
||||
"false_positives": 0,
|
||||
"matched": 1,
|
||||
"missed": 0,
|
||||
"rationale": "rule matched expected findings with no false positives",
|
||||
"rule_id": "python-path-traversal"
|
||||
},
|
||||
{
|
||||
"action": "keep",
|
||||
"false_positives": 0,
|
||||
"matched": 1,
|
||||
"missed": 0,
|
||||
"rationale": "rule matched expected findings with no false positives",
|
||||
"rule_id": "python-shell-true"
|
||||
},
|
||||
{
|
||||
"action": "keep",
|
||||
"false_positives": 0,
|
||||
"matched": 2,
|
||||
"missed": 0,
|
||||
"rationale": "rule matched expected findings with no false positives",
|
||||
"rule_id": "python-sql-injection"
|
||||
},
|
||||
{
|
||||
"action": "keep",
|
||||
"false_positives": 0,
|
||||
"matched": 1,
|
||||
"missed": 0,
|
||||
"rationale": "rule matched expected findings with no false positives",
|
||||
"rule_id": "python-ssrf"
|
||||
},
|
||||
{
|
||||
"action": "keep",
|
||||
"false_positives": 0,
|
||||
"matched": 1,
|
||||
"missed": 0,
|
||||
"rationale": "rule matched expected findings with no false positives",
|
||||
"rule_id": "python-ssti"
|
||||
},
|
||||
{
|
||||
"action": "keep",
|
||||
"false_positives": 0,
|
||||
"matched": 2,
|
||||
"missed": 0,
|
||||
"rationale": "rule matched expected findings with no false positives",
|
||||
"rule_id": "python-unsafe-deserialization"
|
||||
},
|
||||
{
|
||||
"action": "keep",
|
||||
"false_positives": 0,
|
||||
"matched": 1,
|
||||
"missed": 0,
|
||||
"rationale": "rule matched expected findings with no false positives",
|
||||
"rule_id": "python-unsafe-eval"
|
||||
},
|
||||
{
|
||||
"action": "keep",
|
||||
"false_positives": 0,
|
||||
"matched": 1,
|
||||
"missed": 0,
|
||||
"rationale": "rule matched expected findings with no false positives",
|
||||
"rule_id": "python-weak-hash"
|
||||
}
|
||||
],
|
||||
"scan_run_dir": "<ROOT>/benchmarks/fixtures/python-vulnerable/.openultrasast/runs/<RUN_ID>"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"cwe": "CWE-89",
|
||||
"evidence": "execute runs a SQL query string built from request input on a prior line",
|
||||
"failed_stage": "benchmark_ground_truth_matching",
|
||||
"next_improvement_candidate": "language_hunter",
|
||||
"path": "app.py",
|
||||
"reason": "no OpenUltraSAST finding matched the expected benchmark vulnerability",
|
||||
"rule_id": null,
|
||||
"vulnerability_class": "sql injection"
|
||||
}
|
||||
]
|
||||
+1
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,11 @@
|
||||
scan_id=<RUN_ID>
|
||||
run_dir=<ROOT>/benchmarks/fixtures/python-vulnerable/.openultrasast/runs/<RUN_ID>
|
||||
file_targets=2
|
||||
ranked_targets=2
|
||||
findings=12
|
||||
calibrations_applied=0
|
||||
benchmark_run_id=<RUN_ID>
|
||||
benchmark_run_dir=<ROOT>/benchmarks/fixtures/python-vulnerable/.openultrasast/benchmarks/<RUN_ID>
|
||||
benchmark_expected=13
|
||||
benchmark_matched=12
|
||||
benchmark_missed=1
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"baseline": "this directory (record run A)",
|
||||
"candidate": "record run B: second independent record from a fresh export of the same tree, same venv; not committed",
|
||||
"differences": [],
|
||||
"equal": true,
|
||||
"exclusions": [
|
||||
"JSON keys: created_at, finished, generated_at, runtime_seconds, scan_id, started, timestamp",
|
||||
"JSON keys ending in: _seconds, _ms",
|
||||
"run directory names / run ids (<timestamp>-<8 hex>, benchmark.py create_benchmark_run, run.py create_scan_run) -> <RUN_ID>",
|
||||
"trace/events.jsonl as a whole (timings); never copied",
|
||||
"the export's absolute root path -> <ROOT>; the children's TMPDIR -> <TMP>",
|
||||
"suite: only the outcome per test id is kept (junit durations dropped)",
|
||||
"env.json is a provenance record (commit, freeze hash, instrument checks), not compared"
|
||||
],
|
||||
"raw_bytes_identical": "all 15 files incl. env.json (diff -r empty)"
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"child_env": {
|
||||
"OPENULTRASAST_SKIP_DOTENV": "1",
|
||||
"PYTHONHASHSEED": "0",
|
||||
"api_keys": "every *_API_KEY unset",
|
||||
"config": "empty"
|
||||
},
|
||||
"exclusions": [
|
||||
"JSON keys: created_at, finished, generated_at, runtime_seconds, scan_id, started, timestamp",
|
||||
"JSON keys ending in: _seconds, _ms",
|
||||
"run directory names / run ids (<timestamp>-<8 hex>, benchmark.py create_benchmark_run, run.py create_scan_run) -> <RUN_ID>",
|
||||
"trace/events.jsonl as a whole (timings); never copied",
|
||||
"the export's absolute root path -> <ROOT>; the children's TMPDIR -> <TMP>",
|
||||
"suite: only the outcome per test id is kept (junit durations dropped)",
|
||||
"env.json is a provenance record (commit, freeze hash, instrument checks), not compared"
|
||||
],
|
||||
"freeze_sha256": "0e5ad41948a751e1c60f72738c48f5ac482b24b67e51c77369ab3be51fa418eb",
|
||||
"harnessx_importable": false,
|
||||
"inputs": {
|
||||
"benchmarks/manifests/java-spring-boot-vulnerable.toml": 1032,
|
||||
"benchmarks/manifests/python-vulnerable.toml": 2771,
|
||||
"benchmarks/pairs/catalog.toml": 6052
|
||||
},
|
||||
"instrument_checks": {
|
||||
"benchmark": {
|
||||
"expected": 13,
|
||||
"findings": 12,
|
||||
"matched": 12,
|
||||
"missed": 1
|
||||
},
|
||||
"benchmark_files": {
|
||||
"benchmark_result.json": 5891,
|
||||
"calibration_records.json": 406,
|
||||
"external_baseline_deltas.json": 3
|
||||
},
|
||||
"improve": {
|
||||
"journal_bytes": null,
|
||||
"rounds": 1
|
||||
},
|
||||
"openultrasast_module": "<ROOT>/src/openultrasast/__init__.py",
|
||||
"pairs": {
|
||||
"detected_vuln": 57,
|
||||
"labeled_expected": 342,
|
||||
"labeled_matched": 82,
|
||||
"outcomes": 317,
|
||||
"pair_correct": 32,
|
||||
"pairs": 317
|
||||
},
|
||||
"scan": {
|
||||
"file_targets": 2,
|
||||
"findings": 17
|
||||
},
|
||||
"scan_files": {
|
||||
"findings.json": 20469,
|
||||
"fusion.json": 6284,
|
||||
"harness.json": 189,
|
||||
"manifest.json": 13265,
|
||||
"report.sarif": 40310,
|
||||
"verification.json": 10573
|
||||
},
|
||||
"suite": {
|
||||
"error": 0,
|
||||
"failure": 0,
|
||||
"passed": 1688,
|
||||
"skipped": 36
|
||||
}
|
||||
},
|
||||
"python": "3.11.13",
|
||||
"tree_ish": "ca420bfb99d649f01030b8a28ff3ffa4b32d1b22",
|
||||
"tree_sitter": true
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
manifest=benchmarks/manifests/java-spring-boot-vulnerable.toml
|
||||
target=<ROOT>/benchmarks/fixtures/java-spring-boot-vulnerable
|
||||
round 1: no_proposals | recall 100.00%->100.00% fp 0.00%->0.00% score 73->73 | edits: -
|
||||
rounds=1 accepted=0
|
||||
dry_run=true (no ledger or mechanism store written)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,630 @@
|
||||
{
|
||||
"findings": [
|
||||
{
|
||||
"confidence": "medium",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-flask-debug:app.py:81",
|
||||
"function_name": null,
|
||||
"line": 81,
|
||||
"path": "app.py",
|
||||
"proposed_snippet": null,
|
||||
"ranking_priority": 1.5,
|
||||
"rationale": "Static pattern python-flask-debug (CWE-489) matched debug on line 81; manual verification still required.",
|
||||
"reachability_conditions": [],
|
||||
"reachability_evidence": [],
|
||||
"reachability_status": "unknown",
|
||||
"rung": "suspicion",
|
||||
"severity": "medium",
|
||||
"status": "enabled",
|
||||
"tags": [
|
||||
"crypto",
|
||||
"deserialization",
|
||||
"filesystem_entry",
|
||||
"insecure_default",
|
||||
"syscall_entry"
|
||||
],
|
||||
"title": "Flask debug mode enabled needs review"
|
||||
},
|
||||
{
|
||||
"confidence": "medium",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-os-command:app.py:17",
|
||||
"function_name": "ping",
|
||||
"line": 17,
|
||||
"path": "app.py",
|
||||
"proposed_snippet": null,
|
||||
"ranking_priority": 3.8,
|
||||
"rationale": "Static pattern python-os-command (CWE-78) matched os.system on line 17; manual verification still required.",
|
||||
"reachability_conditions": [],
|
||||
"reachability_evidence": [
|
||||
{
|
||||
"access_declared": false,
|
||||
"access_evidence": [
|
||||
"app.get('/ping')"
|
||||
],
|
||||
"access_level": "public",
|
||||
"conditions": [],
|
||||
"defines_handler": true,
|
||||
"end_line": 17,
|
||||
"function_name": "ping",
|
||||
"kind": "route",
|
||||
"line": 15,
|
||||
"name": "ping",
|
||||
"path": "app.py",
|
||||
"provenance": "entrypoint:route:public",
|
||||
"rationale": "route surface classified as public at http_request",
|
||||
"registered_by": "",
|
||||
"trust_boundary": "http_request"
|
||||
}
|
||||
],
|
||||
"reachability_status": "reachable",
|
||||
"rung": "suspicion",
|
||||
"severity": "critical",
|
||||
"status": "enabled",
|
||||
"tags": [
|
||||
"crypto",
|
||||
"deserialization",
|
||||
"filesystem_entry",
|
||||
"syscall_entry"
|
||||
],
|
||||
"title": "Python OS command execution needs review"
|
||||
},
|
||||
{
|
||||
"confidence": "medium",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-path-traversal:app.py:55",
|
||||
"function_name": "read",
|
||||
"line": 55,
|
||||
"path": "app.py",
|
||||
"proposed_snippet": null,
|
||||
"ranking_priority": 3.8,
|
||||
"rationale": "Static pattern python-path-traversal (CWE-22) matched open on line 55; manual verification still required.",
|
||||
"reachability_conditions": [],
|
||||
"reachability_evidence": [
|
||||
{
|
||||
"access_declared": false,
|
||||
"access_evidence": [
|
||||
"app.get('/read')"
|
||||
],
|
||||
"access_level": "public",
|
||||
"conditions": [],
|
||||
"defines_handler": true,
|
||||
"end_line": 55,
|
||||
"function_name": "read",
|
||||
"kind": "route",
|
||||
"line": 54,
|
||||
"name": "read",
|
||||
"path": "app.py",
|
||||
"provenance": "entrypoint:route:public",
|
||||
"rationale": "route surface classified as public at http_request",
|
||||
"registered_by": "",
|
||||
"trust_boundary": "http_request"
|
||||
}
|
||||
],
|
||||
"reachability_status": "reachable",
|
||||
"rung": "suspicion",
|
||||
"severity": "medium",
|
||||
"status": "enabled",
|
||||
"tags": [
|
||||
"crypto",
|
||||
"deserialization",
|
||||
"filesystem_entry",
|
||||
"syscall_entry"
|
||||
],
|
||||
"title": "Python file open with external input needs review"
|
||||
},
|
||||
{
|
||||
"confidence": "medium",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-shell-true:app.py:23",
|
||||
"function_name": "run",
|
||||
"line": 23,
|
||||
"path": "app.py",
|
||||
"proposed_snippet": null,
|
||||
"ranking_priority": 3.8,
|
||||
"rationale": "Static pattern python-shell-true (CWE-78) matched subprocess.check_output on line 23; manual verification still required.",
|
||||
"reachability_conditions": [],
|
||||
"reachability_evidence": [
|
||||
{
|
||||
"access_declared": false,
|
||||
"access_evidence": [
|
||||
"app.get('/run')"
|
||||
],
|
||||
"access_level": "public",
|
||||
"conditions": [],
|
||||
"defines_handler": true,
|
||||
"end_line": 23,
|
||||
"function_name": "run",
|
||||
"kind": "route",
|
||||
"line": 21,
|
||||
"name": "run",
|
||||
"path": "app.py",
|
||||
"provenance": "entrypoint:route:public",
|
||||
"rationale": "route surface classified as public at http_request",
|
||||
"registered_by": "",
|
||||
"trust_boundary": "http_request"
|
||||
}
|
||||
],
|
||||
"reachability_status": "reachable",
|
||||
"rung": "suspicion",
|
||||
"severity": "critical",
|
||||
"status": "enabled",
|
||||
"tags": [
|
||||
"crypto",
|
||||
"deserialization",
|
||||
"filesystem_entry",
|
||||
"syscall_entry"
|
||||
],
|
||||
"title": "Subprocess shell execution needs review"
|
||||
},
|
||||
{
|
||||
"confidence": "medium",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-sql-injection:app.py:44",
|
||||
"function_name": "user",
|
||||
"line": 44,
|
||||
"path": "app.py",
|
||||
"proposed_snippet": null,
|
||||
"ranking_priority": 3.8,
|
||||
"rationale": "Static pattern python-sql-injection (CWE-89) matched .execute on line 44; manual verification still required.",
|
||||
"reachability_conditions": [],
|
||||
"reachability_evidence": [
|
||||
{
|
||||
"access_declared": false,
|
||||
"access_evidence": [
|
||||
"app.get('/user')"
|
||||
],
|
||||
"access_level": "public",
|
||||
"conditions": [],
|
||||
"defines_handler": true,
|
||||
"end_line": 44,
|
||||
"function_name": "user",
|
||||
"kind": "route",
|
||||
"line": 42,
|
||||
"name": "user",
|
||||
"path": "app.py",
|
||||
"provenance": "entrypoint:route:public",
|
||||
"rationale": "route surface classified as public at http_request",
|
||||
"registered_by": "",
|
||||
"trust_boundary": "http_request"
|
||||
}
|
||||
],
|
||||
"reachability_status": "reachable",
|
||||
"rung": "suspicion",
|
||||
"severity": "high",
|
||||
"status": "enabled",
|
||||
"tags": [
|
||||
"crypto",
|
||||
"deserialization",
|
||||
"filesystem_entry",
|
||||
"injection",
|
||||
"syscall_entry"
|
||||
],
|
||||
"title": "Python SQL string composition needs review"
|
||||
},
|
||||
{
|
||||
"confidence": "medium",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-sql-injection:app.py:50",
|
||||
"function_name": "report",
|
||||
"line": 50,
|
||||
"path": "app.py",
|
||||
"proposed_snippet": null,
|
||||
"ranking_priority": 3.8,
|
||||
"rationale": "Static pattern python-sql-injection (CWE-89) matched .execute on line 50; manual verification still required.",
|
||||
"reachability_conditions": [],
|
||||
"reachability_evidence": [
|
||||
{
|
||||
"access_declared": false,
|
||||
"access_evidence": [
|
||||
"app.get('/report')"
|
||||
],
|
||||
"access_level": "public",
|
||||
"conditions": [],
|
||||
"defines_handler": true,
|
||||
"end_line": 50,
|
||||
"function_name": "report",
|
||||
"kind": "route",
|
||||
"line": 48,
|
||||
"name": "report",
|
||||
"path": "app.py",
|
||||
"provenance": "entrypoint:route:public",
|
||||
"rationale": "route surface classified as public at http_request",
|
||||
"registered_by": "",
|
||||
"trust_boundary": "http_request"
|
||||
}
|
||||
],
|
||||
"reachability_status": "reachable",
|
||||
"rung": "suspicion",
|
||||
"severity": "high",
|
||||
"status": "enabled",
|
||||
"tags": [
|
||||
"crypto",
|
||||
"deserialization",
|
||||
"filesystem_entry",
|
||||
"injection",
|
||||
"syscall_entry"
|
||||
],
|
||||
"title": "Python SQL string composition needs review"
|
||||
},
|
||||
{
|
||||
"confidence": "medium",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-ssrf:app.py:60",
|
||||
"function_name": "fetch",
|
||||
"line": 60,
|
||||
"path": "app.py",
|
||||
"proposed_snippet": null,
|
||||
"ranking_priority": 3.8,
|
||||
"rationale": "Static pattern python-ssrf (CWE-918) matched requests.get on line 60; manual verification still required.",
|
||||
"reachability_conditions": [],
|
||||
"reachability_evidence": [
|
||||
{
|
||||
"access_declared": false,
|
||||
"access_evidence": [
|
||||
"app.get('/fetch')"
|
||||
],
|
||||
"access_level": "public",
|
||||
"conditions": [],
|
||||
"defines_handler": true,
|
||||
"end_line": 60,
|
||||
"function_name": "fetch",
|
||||
"kind": "route",
|
||||
"line": 59,
|
||||
"name": "fetch",
|
||||
"path": "app.py",
|
||||
"provenance": "entrypoint:route:public",
|
||||
"rationale": "route surface classified as public at http_request",
|
||||
"registered_by": "",
|
||||
"trust_boundary": "http_request"
|
||||
}
|
||||
],
|
||||
"reachability_status": "reachable",
|
||||
"rung": "suspicion",
|
||||
"severity": "medium",
|
||||
"status": "enabled",
|
||||
"tags": [
|
||||
"crypto",
|
||||
"deserialization",
|
||||
"filesystem_entry",
|
||||
"network_entry",
|
||||
"syscall_entry"
|
||||
],
|
||||
"title": "Python outbound request with external input needs review"
|
||||
},
|
||||
{
|
||||
"confidence": "medium",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-ssti:app.py:70",
|
||||
"function_name": "render",
|
||||
"line": 70,
|
||||
"path": "app.py",
|
||||
"proposed_snippet": null,
|
||||
"ranking_priority": 3.8,
|
||||
"rationale": "Static pattern python-ssti (CWE-94) matched render_template_string on line 70; manual verification still required.",
|
||||
"reachability_conditions": [],
|
||||
"reachability_evidence": [
|
||||
{
|
||||
"access_declared": false,
|
||||
"access_evidence": [
|
||||
"app.get('/render')"
|
||||
],
|
||||
"access_level": "public",
|
||||
"conditions": [],
|
||||
"defines_handler": true,
|
||||
"end_line": 70,
|
||||
"function_name": "render",
|
||||
"kind": "route",
|
||||
"line": 69,
|
||||
"name": "render",
|
||||
"path": "app.py",
|
||||
"provenance": "entrypoint:route:public",
|
||||
"rationale": "route surface classified as public at http_request",
|
||||
"registered_by": "",
|
||||
"trust_boundary": "http_request"
|
||||
}
|
||||
],
|
||||
"reachability_status": "reachable",
|
||||
"rung": "suspicion",
|
||||
"severity": "medium",
|
||||
"status": "enabled",
|
||||
"tags": [
|
||||
"crypto",
|
||||
"deserialization",
|
||||
"filesystem_entry",
|
||||
"injection",
|
||||
"syscall_entry"
|
||||
],
|
||||
"title": "Python server-side template injection needs review"
|
||||
},
|
||||
{
|
||||
"confidence": "medium",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-unsafe-deserialization:app.py:33",
|
||||
"function_name": "load",
|
||||
"line": 33,
|
||||
"path": "app.py",
|
||||
"proposed_snippet": null,
|
||||
"ranking_priority": 3.8,
|
||||
"rationale": "Static pattern python-unsafe-deserialization (CWE-502) matched pickle.loads on line 33; manual verification still required.",
|
||||
"reachability_conditions": [],
|
||||
"reachability_evidence": [
|
||||
{
|
||||
"access_declared": false,
|
||||
"access_evidence": [
|
||||
"app.get('/load')"
|
||||
],
|
||||
"access_level": "public",
|
||||
"conditions": [],
|
||||
"defines_handler": true,
|
||||
"end_line": 33,
|
||||
"function_name": "load",
|
||||
"kind": "route",
|
||||
"line": 32,
|
||||
"name": "load",
|
||||
"path": "app.py",
|
||||
"provenance": "entrypoint:route:public",
|
||||
"rationale": "route surface classified as public at http_request",
|
||||
"registered_by": "",
|
||||
"trust_boundary": "http_request"
|
||||
}
|
||||
],
|
||||
"reachability_status": "reachable",
|
||||
"rung": "suspicion",
|
||||
"severity": "medium",
|
||||
"status": "enabled",
|
||||
"tags": [
|
||||
"crypto",
|
||||
"deserialization",
|
||||
"filesystem_entry",
|
||||
"syscall_entry"
|
||||
],
|
||||
"title": "Unsafe Python deserialization needs review"
|
||||
},
|
||||
{
|
||||
"confidence": "medium",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-unsafe-deserialization:app.py:38",
|
||||
"function_name": "config",
|
||||
"line": 38,
|
||||
"path": "app.py",
|
||||
"proposed_snippet": null,
|
||||
"ranking_priority": 3.8,
|
||||
"rationale": "Static pattern python-unsafe-deserialization (CWE-502) matched yaml.load on line 38; manual verification still required.",
|
||||
"reachability_conditions": [],
|
||||
"reachability_evidence": [
|
||||
{
|
||||
"access_declared": false,
|
||||
"access_evidence": [
|
||||
"app.get('/config')"
|
||||
],
|
||||
"access_level": "public",
|
||||
"conditions": [],
|
||||
"defines_handler": true,
|
||||
"end_line": 38,
|
||||
"function_name": "config",
|
||||
"kind": "route",
|
||||
"line": 37,
|
||||
"name": "config",
|
||||
"path": "app.py",
|
||||
"provenance": "entrypoint:route:public",
|
||||
"rationale": "route surface classified as public at http_request",
|
||||
"registered_by": "",
|
||||
"trust_boundary": "http_request"
|
||||
}
|
||||
],
|
||||
"reachability_status": "reachable",
|
||||
"rung": "suspicion",
|
||||
"severity": "medium",
|
||||
"status": "enabled",
|
||||
"tags": [
|
||||
"crypto",
|
||||
"deserialization",
|
||||
"filesystem_entry",
|
||||
"syscall_entry"
|
||||
],
|
||||
"title": "Unsafe Python deserialization needs review"
|
||||
},
|
||||
{
|
||||
"confidence": "medium",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-unsafe-eval:app.py:28",
|
||||
"function_name": "calc",
|
||||
"line": 28,
|
||||
"path": "app.py",
|
||||
"proposed_snippet": null,
|
||||
"ranking_priority": 3.8,
|
||||
"rationale": "Static pattern python-unsafe-eval (CWE-95) matched eval on line 28; manual verification still required.",
|
||||
"reachability_conditions": [],
|
||||
"reachability_evidence": [
|
||||
{
|
||||
"access_declared": false,
|
||||
"access_evidence": [
|
||||
"app.get('/calc')"
|
||||
],
|
||||
"access_level": "public",
|
||||
"conditions": [],
|
||||
"defines_handler": true,
|
||||
"end_line": 28,
|
||||
"function_name": "calc",
|
||||
"kind": "route",
|
||||
"line": 27,
|
||||
"name": "calc",
|
||||
"path": "app.py",
|
||||
"provenance": "entrypoint:route:public",
|
||||
"rationale": "route surface classified as public at http_request",
|
||||
"registered_by": "",
|
||||
"trust_boundary": "http_request"
|
||||
}
|
||||
],
|
||||
"reachability_status": "reachable",
|
||||
"rung": "suspicion",
|
||||
"severity": "critical",
|
||||
"status": "enabled",
|
||||
"tags": [
|
||||
"crypto",
|
||||
"deserialization",
|
||||
"filesystem_entry",
|
||||
"syscall_entry"
|
||||
],
|
||||
"title": "Dynamic Python execution needs review"
|
||||
},
|
||||
{
|
||||
"confidence": "medium",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-weak-hash:app.py:65",
|
||||
"function_name": "token",
|
||||
"line": 65,
|
||||
"path": "app.py",
|
||||
"proposed_snippet": null,
|
||||
"ranking_priority": 3.8,
|
||||
"rationale": "Static pattern python-weak-hash (CWE-327) matched md5 on line 65; manual verification still required.",
|
||||
"reachability_conditions": [],
|
||||
"reachability_evidence": [
|
||||
{
|
||||
"access_declared": false,
|
||||
"access_evidence": [
|
||||
"app.get('/token')"
|
||||
],
|
||||
"access_level": "public",
|
||||
"conditions": [],
|
||||
"defines_handler": true,
|
||||
"end_line": 65,
|
||||
"function_name": "token",
|
||||
"kind": "route",
|
||||
"line": 64,
|
||||
"name": "token",
|
||||
"path": "app.py",
|
||||
"provenance": "entrypoint:route:public",
|
||||
"rationale": "route surface classified as public at http_request",
|
||||
"registered_by": "",
|
||||
"trust_boundary": "http_request"
|
||||
}
|
||||
],
|
||||
"reachability_status": "reachable",
|
||||
"rung": "suspicion",
|
||||
"severity": "medium",
|
||||
"status": "enabled",
|
||||
"tags": [
|
||||
"crypto",
|
||||
"deserialization",
|
||||
"filesystem_entry",
|
||||
"syscall_entry"
|
||||
],
|
||||
"title": "Python weak hash needs review"
|
||||
},
|
||||
{
|
||||
"confidence": "medium",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "overlay-coverage:app.py:77:execute",
|
||||
"function_name": null,
|
||||
"line": 77,
|
||||
"path": "app.py",
|
||||
"proposed_snippet": null,
|
||||
"ranking_priority": 0.0,
|
||||
"rationale": "uninventoried sink execute reached from request",
|
||||
"reachability_conditions": [],
|
||||
"reachability_evidence": [],
|
||||
"reachability_status": "unknown",
|
||||
"rung": "suspicion",
|
||||
"severity": "high",
|
||||
"status": "enabled",
|
||||
"tags": [
|
||||
"overlay-originated"
|
||||
],
|
||||
"title": "Overlay coverage: execute"
|
||||
},
|
||||
{
|
||||
"confidence": "medium",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "overlay-coverage:safe.py:11:os.system",
|
||||
"function_name": null,
|
||||
"line": 11,
|
||||
"path": "safe.py",
|
||||
"proposed_snippet": null,
|
||||
"ranking_priority": 0.0,
|
||||
"rationale": "uninventoried sink os.system reached from parameter",
|
||||
"reachability_conditions": [],
|
||||
"reachability_evidence": [],
|
||||
"reachability_status": "unknown",
|
||||
"rung": "suspicion",
|
||||
"severity": "critical",
|
||||
"status": "enabled",
|
||||
"tags": [
|
||||
"overlay-originated"
|
||||
],
|
||||
"title": "Overlay coverage: os.system"
|
||||
},
|
||||
{
|
||||
"confidence": "low",
|
||||
"evidence_level": "suspicion",
|
||||
"finding_id": "obligation:protected_read:app.py:44:identity_constraint",
|
||||
"function_name": "user",
|
||||
"line": 44,
|
||||
"path": "app.py",
|
||||
"proposed_snippet": null,
|
||||
"ranking_priority": 0.3,
|
||||
"rationale": "protected_read on users in user is reached without a dominating identity_constraint. Why the obligation exists: the operation fact alone (function-local mode). Label: function_local.",
|
||||
"reachability_conditions": [],
|
||||
"reachability_evidence": [],
|
||||
"reachability_status": "unknown",
|
||||
"rung": "suspicion",
|
||||
"severity": "high",
|
||||
"status": "enabled",
|
||||
"tags": [
|
||||
"obligation:protected_read",
|
||||
"discharger:identity_constraint",
|
||||
"obligation_evidence:function_local",
|
||||
"resource:users"
|
||||
],
|
||||
"title": "Obligation not discharged: protected_read on users without identity_constraint"
|
||||
},
|
||||
{
|
||||
"confidence": "low",
|
||||
"evidence_level": "suspicion",
|
||||
"finding_id": "obligation:protected_read:app.py:50:identity_constraint",
|
||||
"function_name": "report",
|
||||
"line": 50,
|
||||
"path": "app.py",
|
||||
"proposed_snippet": null,
|
||||
"ranking_priority": 0.3,
|
||||
"rationale": "protected_read on reports in report is reached without a dominating identity_constraint. Why the obligation exists: the operation fact alone (function-local mode). Label: function_local.",
|
||||
"reachability_conditions": [],
|
||||
"reachability_evidence": [],
|
||||
"reachability_status": "unknown",
|
||||
"rung": "suspicion",
|
||||
"severity": "high",
|
||||
"status": "enabled",
|
||||
"tags": [
|
||||
"obligation:protected_read",
|
||||
"discharger:identity_constraint",
|
||||
"obligation_evidence:function_local",
|
||||
"resource:reports"
|
||||
],
|
||||
"title": "Obligation not discharged: protected_read on reports without identity_constraint"
|
||||
},
|
||||
{
|
||||
"confidence": "low",
|
||||
"evidence_level": "suspicion",
|
||||
"finding_id": "obligation:protected_read:app.py:77:identity_constraint",
|
||||
"function_name": "search",
|
||||
"line": 77,
|
||||
"path": "app.py",
|
||||
"proposed_snippet": null,
|
||||
"ranking_priority": 0.3,
|
||||
"rationale": "protected_read on a resource in search is reached without a dominating identity_constraint. Why the obligation exists: the operation fact alone (function-local mode). Label: function_local.",
|
||||
"reachability_conditions": [],
|
||||
"reachability_evidence": [],
|
||||
"reachability_status": "unknown",
|
||||
"rung": "suspicion",
|
||||
"severity": "high",
|
||||
"status": "enabled",
|
||||
"tags": [
|
||||
"obligation:protected_read",
|
||||
"discharger:identity_constraint",
|
||||
"obligation_evidence:function_local"
|
||||
],
|
||||
"title": "Obligation not discharged: protected_read on a resource without identity_constraint"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
[
|
||||
{
|
||||
"decision_source": "deterministic-reconciler",
|
||||
"degradations": [],
|
||||
"disposition": "accepted",
|
||||
"finding_id": "python-os-command:app.py:17",
|
||||
"model_ids": [],
|
||||
"panels": [
|
||||
{
|
||||
"confidence": 0.75,
|
||||
"disposition": "accepted",
|
||||
"false_positive_case": "Reachability or sanitizer evidence could still neutralize the sink.",
|
||||
"leaning": "vulnerability",
|
||||
"model_id": null,
|
||||
"rationale": "reachability=reachable, severity=critical, verified=True",
|
||||
"role": "panel_a",
|
||||
"vulnerability_case": "Static corroboration exists and function-level reachability evidence is attached."
|
||||
},
|
||||
{
|
||||
"confidence": 0.6,
|
||||
"disposition": "accepted",
|
||||
"false_positive_case": "Impact and exploitability still require deeper verifier or dynamic evidence.",
|
||||
"leaning": "false_positive",
|
||||
"model_id": null,
|
||||
"rationale": "reachable=True, rejected=False, verified=True",
|
||||
"role": "panel_b",
|
||||
"vulnerability_case": "A tainted sink shape exists in the source."
|
||||
}
|
||||
],
|
||||
"triggered": true,
|
||||
"triggers": [
|
||||
"gates_risky_fix",
|
||||
"severity:critical"
|
||||
],
|
||||
"votes": {
|
||||
"accepted": 2
|
||||
},
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"decision_source": "deterministic-reconciler",
|
||||
"degradations": [],
|
||||
"disposition": "accepted",
|
||||
"finding_id": "python-shell-true:app.py:23",
|
||||
"model_ids": [],
|
||||
"panels": [
|
||||
{
|
||||
"confidence": 0.75,
|
||||
"disposition": "accepted",
|
||||
"false_positive_case": "Reachability or sanitizer evidence could still neutralize the sink.",
|
||||
"leaning": "vulnerability",
|
||||
"model_id": null,
|
||||
"rationale": "reachability=reachable, severity=critical, verified=True",
|
||||
"role": "panel_a",
|
||||
"vulnerability_case": "Static corroboration exists and function-level reachability evidence is attached."
|
||||
},
|
||||
{
|
||||
"confidence": 0.6,
|
||||
"disposition": "accepted",
|
||||
"false_positive_case": "Impact and exploitability still require deeper verifier or dynamic evidence.",
|
||||
"leaning": "false_positive",
|
||||
"model_id": null,
|
||||
"rationale": "reachable=True, rejected=False, verified=True",
|
||||
"role": "panel_b",
|
||||
"vulnerability_case": "A tainted sink shape exists in the source."
|
||||
}
|
||||
],
|
||||
"triggered": true,
|
||||
"triggers": [
|
||||
"gates_risky_fix",
|
||||
"severity:critical"
|
||||
],
|
||||
"votes": {
|
||||
"accepted": 2
|
||||
},
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"decision_source": "deterministic-reconciler",
|
||||
"degradations": [],
|
||||
"disposition": "accepted",
|
||||
"finding_id": "python-sql-injection:app.py:44",
|
||||
"model_ids": [],
|
||||
"panels": [
|
||||
{
|
||||
"confidence": 0.75,
|
||||
"disposition": "accepted",
|
||||
"false_positive_case": "Reachability or sanitizer evidence could still neutralize the sink.",
|
||||
"leaning": "vulnerability",
|
||||
"model_id": null,
|
||||
"rationale": "reachability=reachable, severity=high, verified=True",
|
||||
"role": "panel_a",
|
||||
"vulnerability_case": "Static corroboration exists and function-level reachability evidence is attached."
|
||||
},
|
||||
{
|
||||
"confidence": 0.6,
|
||||
"disposition": "accepted",
|
||||
"false_positive_case": "Impact and exploitability still require deeper verifier or dynamic evidence.",
|
||||
"leaning": "false_positive",
|
||||
"model_id": null,
|
||||
"rationale": "reachable=True, rejected=False, verified=True",
|
||||
"role": "panel_b",
|
||||
"vulnerability_case": "A tainted sink shape exists in the source."
|
||||
}
|
||||
],
|
||||
"triggered": true,
|
||||
"triggers": [
|
||||
"gates_risky_fix",
|
||||
"severity:high"
|
||||
],
|
||||
"votes": {
|
||||
"accepted": 2
|
||||
},
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"decision_source": "deterministic-reconciler",
|
||||
"degradations": [],
|
||||
"disposition": "accepted",
|
||||
"finding_id": "python-sql-injection:app.py:50",
|
||||
"model_ids": [],
|
||||
"panels": [
|
||||
{
|
||||
"confidence": 0.75,
|
||||
"disposition": "accepted",
|
||||
"false_positive_case": "Reachability or sanitizer evidence could still neutralize the sink.",
|
||||
"leaning": "vulnerability",
|
||||
"model_id": null,
|
||||
"rationale": "reachability=reachable, severity=high, verified=True",
|
||||
"role": "panel_a",
|
||||
"vulnerability_case": "Static corroboration exists and function-level reachability evidence is attached."
|
||||
},
|
||||
{
|
||||
"confidence": 0.6,
|
||||
"disposition": "accepted",
|
||||
"false_positive_case": "Impact and exploitability still require deeper verifier or dynamic evidence.",
|
||||
"leaning": "false_positive",
|
||||
"model_id": null,
|
||||
"rationale": "reachable=True, rejected=False, verified=True",
|
||||
"role": "panel_b",
|
||||
"vulnerability_case": "A tainted sink shape exists in the source."
|
||||
}
|
||||
],
|
||||
"triggered": true,
|
||||
"triggers": [
|
||||
"gates_risky_fix",
|
||||
"severity:high"
|
||||
],
|
||||
"votes": {
|
||||
"accepted": 2
|
||||
},
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"decision_source": "deterministic-reconciler",
|
||||
"degradations": [],
|
||||
"disposition": "accepted",
|
||||
"finding_id": "python-unsafe-eval:app.py:28",
|
||||
"model_ids": [],
|
||||
"panels": [
|
||||
{
|
||||
"confidence": 0.75,
|
||||
"disposition": "accepted",
|
||||
"false_positive_case": "Reachability or sanitizer evidence could still neutralize the sink.",
|
||||
"leaning": "vulnerability",
|
||||
"model_id": null,
|
||||
"rationale": "reachability=reachable, severity=critical, verified=True",
|
||||
"role": "panel_a",
|
||||
"vulnerability_case": "Static corroboration exists and function-level reachability evidence is attached."
|
||||
},
|
||||
{
|
||||
"confidence": 0.6,
|
||||
"disposition": "accepted",
|
||||
"false_positive_case": "Impact and exploitability still require deeper verifier or dynamic evidence.",
|
||||
"leaning": "false_positive",
|
||||
"model_id": null,
|
||||
"rationale": "reachable=True, rejected=False, verified=True",
|
||||
"role": "panel_b",
|
||||
"vulnerability_case": "A tainted sink shape exists in the source."
|
||||
}
|
||||
],
|
||||
"triggered": true,
|
||||
"triggers": [
|
||||
"gates_risky_fix",
|
||||
"severity:critical"
|
||||
],
|
||||
"votes": {
|
||||
"accepted": 2
|
||||
},
|
||||
"warnings": []
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"contract_mode": "strict",
|
||||
"model_roles": {
|
||||
"hunter": null,
|
||||
"patcher": null,
|
||||
"ranker": null,
|
||||
"verifier": null
|
||||
},
|
||||
"processor_versions": {},
|
||||
"prompt_hashes": {}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
{
|
||||
"artifacts": {
|
||||
"complexity_map": "complexity_map.json",
|
||||
"findings": "findings.json",
|
||||
"fusion": "fusion.json",
|
||||
"markdown": "report.md",
|
||||
"overlay": "overlay.json",
|
||||
"sarif": "report.sarif",
|
||||
"score": "score.json",
|
||||
"trajectories": "traces/hunter_trajectories.jsonl",
|
||||
"verification": "verification.json"
|
||||
},
|
||||
"complexity": {
|
||||
"heuristic_only": true,
|
||||
"hotspot_count": 14
|
||||
},
|
||||
"degradations": [
|
||||
{
|
||||
"detail": "no path records; obligations evaluated within entry-point handlers only",
|
||||
"files": [
|
||||
"app.py",
|
||||
"safe.py"
|
||||
],
|
||||
"language": "python",
|
||||
"reason": "obligations_function_local",
|
||||
"stage": "obligations"
|
||||
},
|
||||
{
|
||||
"detail": "no hunter client or model",
|
||||
"reason": "intent_adjudication_unavailable",
|
||||
"stage": "obligations"
|
||||
},
|
||||
{
|
||||
"reason": "cpg_unavailable",
|
||||
"stage": "model"
|
||||
},
|
||||
{
|
||||
"reason": "hunter_model_unavailable",
|
||||
"stage": "map"
|
||||
}
|
||||
],
|
||||
"findings": [
|
||||
{
|
||||
"artifact_refs": {
|
||||
"findings_json": "findings.json",
|
||||
"markdown": "report.md",
|
||||
"sarif": "report.sarif",
|
||||
"trajectories_jsonl": "traces/hunter_trajectories.jsonl",
|
||||
"verification_json": "verification.json"
|
||||
},
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-flask-debug:app.py:81",
|
||||
"line": 81,
|
||||
"path": "app.py",
|
||||
"rung": "suspicion",
|
||||
"severity": "medium",
|
||||
"verification_status": "rejected"
|
||||
},
|
||||
{
|
||||
"artifact_refs": {
|
||||
"findings_json": "findings.json",
|
||||
"markdown": "report.md",
|
||||
"sarif": "report.sarif",
|
||||
"trajectories_jsonl": "traces/hunter_trajectories.jsonl",
|
||||
"verification_json": "verification.json"
|
||||
},
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-os-command:app.py:17",
|
||||
"line": 17,
|
||||
"path": "app.py",
|
||||
"rung": "suspicion",
|
||||
"severity": "critical",
|
||||
"verification_status": "accepted"
|
||||
},
|
||||
{
|
||||
"artifact_refs": {
|
||||
"findings_json": "findings.json",
|
||||
"markdown": "report.md",
|
||||
"sarif": "report.sarif",
|
||||
"trajectories_jsonl": "traces/hunter_trajectories.jsonl",
|
||||
"verification_json": "verification.json"
|
||||
},
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-path-traversal:app.py:55",
|
||||
"line": 55,
|
||||
"path": "app.py",
|
||||
"rung": "suspicion",
|
||||
"severity": "medium",
|
||||
"verification_status": "accepted"
|
||||
},
|
||||
{
|
||||
"artifact_refs": {
|
||||
"findings_json": "findings.json",
|
||||
"markdown": "report.md",
|
||||
"sarif": "report.sarif",
|
||||
"trajectories_jsonl": "traces/hunter_trajectories.jsonl",
|
||||
"verification_json": "verification.json"
|
||||
},
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-shell-true:app.py:23",
|
||||
"line": 23,
|
||||
"path": "app.py",
|
||||
"rung": "suspicion",
|
||||
"severity": "critical",
|
||||
"verification_status": "accepted"
|
||||
},
|
||||
{
|
||||
"artifact_refs": {
|
||||
"findings_json": "findings.json",
|
||||
"markdown": "report.md",
|
||||
"sarif": "report.sarif",
|
||||
"trajectories_jsonl": "traces/hunter_trajectories.jsonl",
|
||||
"verification_json": "verification.json"
|
||||
},
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-sql-injection:app.py:44",
|
||||
"line": 44,
|
||||
"path": "app.py",
|
||||
"rung": "suspicion",
|
||||
"severity": "high",
|
||||
"verification_status": "accepted"
|
||||
},
|
||||
{
|
||||
"artifact_refs": {
|
||||
"findings_json": "findings.json",
|
||||
"markdown": "report.md",
|
||||
"sarif": "report.sarif",
|
||||
"trajectories_jsonl": "traces/hunter_trajectories.jsonl",
|
||||
"verification_json": "verification.json"
|
||||
},
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-sql-injection:app.py:50",
|
||||
"line": 50,
|
||||
"path": "app.py",
|
||||
"rung": "suspicion",
|
||||
"severity": "high",
|
||||
"verification_status": "accepted"
|
||||
},
|
||||
{
|
||||
"artifact_refs": {
|
||||
"findings_json": "findings.json",
|
||||
"markdown": "report.md",
|
||||
"sarif": "report.sarif",
|
||||
"trajectories_jsonl": "traces/hunter_trajectories.jsonl",
|
||||
"verification_json": "verification.json"
|
||||
},
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-ssrf:app.py:60",
|
||||
"line": 60,
|
||||
"path": "app.py",
|
||||
"rung": "suspicion",
|
||||
"severity": "medium",
|
||||
"verification_status": "accepted"
|
||||
},
|
||||
{
|
||||
"artifact_refs": {
|
||||
"findings_json": "findings.json",
|
||||
"markdown": "report.md",
|
||||
"sarif": "report.sarif",
|
||||
"trajectories_jsonl": "traces/hunter_trajectories.jsonl",
|
||||
"verification_json": "verification.json"
|
||||
},
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-ssti:app.py:70",
|
||||
"line": 70,
|
||||
"path": "app.py",
|
||||
"rung": "suspicion",
|
||||
"severity": "medium",
|
||||
"verification_status": "accepted"
|
||||
},
|
||||
{
|
||||
"artifact_refs": {
|
||||
"findings_json": "findings.json",
|
||||
"markdown": "report.md",
|
||||
"sarif": "report.sarif",
|
||||
"trajectories_jsonl": "traces/hunter_trajectories.jsonl",
|
||||
"verification_json": "verification.json"
|
||||
},
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-unsafe-deserialization:app.py:33",
|
||||
"line": 33,
|
||||
"path": "app.py",
|
||||
"rung": "suspicion",
|
||||
"severity": "medium",
|
||||
"verification_status": "accepted"
|
||||
},
|
||||
{
|
||||
"artifact_refs": {
|
||||
"findings_json": "findings.json",
|
||||
"markdown": "report.md",
|
||||
"sarif": "report.sarif",
|
||||
"trajectories_jsonl": "traces/hunter_trajectories.jsonl",
|
||||
"verification_json": "verification.json"
|
||||
},
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-unsafe-deserialization:app.py:38",
|
||||
"line": 38,
|
||||
"path": "app.py",
|
||||
"rung": "suspicion",
|
||||
"severity": "medium",
|
||||
"verification_status": "accepted"
|
||||
},
|
||||
{
|
||||
"artifact_refs": {
|
||||
"findings_json": "findings.json",
|
||||
"markdown": "report.md",
|
||||
"sarif": "report.sarif",
|
||||
"trajectories_jsonl": "traces/hunter_trajectories.jsonl",
|
||||
"verification_json": "verification.json"
|
||||
},
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-unsafe-eval:app.py:28",
|
||||
"line": 28,
|
||||
"path": "app.py",
|
||||
"rung": "suspicion",
|
||||
"severity": "critical",
|
||||
"verification_status": "accepted"
|
||||
},
|
||||
{
|
||||
"artifact_refs": {
|
||||
"findings_json": "findings.json",
|
||||
"markdown": "report.md",
|
||||
"sarif": "report.sarif",
|
||||
"trajectories_jsonl": "traces/hunter_trajectories.jsonl",
|
||||
"verification_json": "verification.json"
|
||||
},
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-weak-hash:app.py:65",
|
||||
"line": 65,
|
||||
"path": "app.py",
|
||||
"rung": "suspicion",
|
||||
"severity": "medium",
|
||||
"verification_status": "accepted"
|
||||
},
|
||||
{
|
||||
"artifact_refs": {
|
||||
"findings_json": "findings.json",
|
||||
"markdown": "report.md",
|
||||
"sarif": "report.sarif",
|
||||
"trajectories_jsonl": "traces/hunter_trajectories.jsonl",
|
||||
"verification_json": "verification.json"
|
||||
},
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "overlay-coverage:app.py:77:execute",
|
||||
"line": 77,
|
||||
"path": "app.py",
|
||||
"rung": "suspicion",
|
||||
"severity": "high",
|
||||
"verification_status": "not_run"
|
||||
},
|
||||
{
|
||||
"artifact_refs": {
|
||||
"findings_json": "findings.json",
|
||||
"markdown": "report.md",
|
||||
"sarif": "report.sarif",
|
||||
"trajectories_jsonl": "traces/hunter_trajectories.jsonl",
|
||||
"verification_json": "verification.json"
|
||||
},
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "overlay-coverage:safe.py:11:os.system",
|
||||
"line": 11,
|
||||
"path": "safe.py",
|
||||
"rung": "suspicion",
|
||||
"severity": "critical",
|
||||
"verification_status": "not_run"
|
||||
},
|
||||
{
|
||||
"artifact_refs": {
|
||||
"findings_json": "findings.json",
|
||||
"markdown": "report.md",
|
||||
"sarif": "report.sarif",
|
||||
"trajectories_jsonl": "traces/hunter_trajectories.jsonl",
|
||||
"verification_json": "verification.json"
|
||||
},
|
||||
"evidence_level": "suspicion",
|
||||
"finding_id": "obligation:protected_read:app.py:44:identity_constraint",
|
||||
"line": 44,
|
||||
"path": "app.py",
|
||||
"rung": "suspicion",
|
||||
"severity": "high",
|
||||
"verification_status": "not_run"
|
||||
},
|
||||
{
|
||||
"artifact_refs": {
|
||||
"findings_json": "findings.json",
|
||||
"markdown": "report.md",
|
||||
"sarif": "report.sarif",
|
||||
"trajectories_jsonl": "traces/hunter_trajectories.jsonl",
|
||||
"verification_json": "verification.json"
|
||||
},
|
||||
"evidence_level": "suspicion",
|
||||
"finding_id": "obligation:protected_read:app.py:50:identity_constraint",
|
||||
"line": 50,
|
||||
"path": "app.py",
|
||||
"rung": "suspicion",
|
||||
"severity": "high",
|
||||
"verification_status": "not_run"
|
||||
},
|
||||
{
|
||||
"artifact_refs": {
|
||||
"findings_json": "findings.json",
|
||||
"markdown": "report.md",
|
||||
"sarif": "report.sarif",
|
||||
"trajectories_jsonl": "traces/hunter_trajectories.jsonl",
|
||||
"verification_json": "verification.json"
|
||||
},
|
||||
"evidence_level": "suspicion",
|
||||
"finding_id": "obligation:protected_read:app.py:77:identity_constraint",
|
||||
"line": 77,
|
||||
"path": "app.py",
|
||||
"rung": "suspicion",
|
||||
"severity": "high",
|
||||
"verification_status": "not_run"
|
||||
}
|
||||
],
|
||||
"fusion": [
|
||||
{
|
||||
"decision_source": "deterministic-reconciler",
|
||||
"disposition": "accepted",
|
||||
"finding_id": "python-os-command:app.py:17",
|
||||
"triggers": [
|
||||
"gates_risky_fix",
|
||||
"severity:critical"
|
||||
]
|
||||
},
|
||||
{
|
||||
"decision_source": "deterministic-reconciler",
|
||||
"disposition": "accepted",
|
||||
"finding_id": "python-shell-true:app.py:23",
|
||||
"triggers": [
|
||||
"gates_risky_fix",
|
||||
"severity:critical"
|
||||
]
|
||||
},
|
||||
{
|
||||
"decision_source": "deterministic-reconciler",
|
||||
"disposition": "accepted",
|
||||
"finding_id": "python-sql-injection:app.py:44",
|
||||
"triggers": [
|
||||
"gates_risky_fix",
|
||||
"severity:high"
|
||||
]
|
||||
},
|
||||
{
|
||||
"decision_source": "deterministic-reconciler",
|
||||
"disposition": "accepted",
|
||||
"finding_id": "python-sql-injection:app.py:50",
|
||||
"triggers": [
|
||||
"gates_risky_fix",
|
||||
"severity:high"
|
||||
]
|
||||
},
|
||||
{
|
||||
"decision_source": "deterministic-reconciler",
|
||||
"disposition": "accepted",
|
||||
"finding_id": "python-unsafe-eval:app.py:28",
|
||||
"triggers": [
|
||||
"gates_risky_fix",
|
||||
"severity:critical"
|
||||
]
|
||||
}
|
||||
],
|
||||
"model": {
|
||||
"by_rung": {},
|
||||
"cost_usd": 0.0,
|
||||
"coverage": [],
|
||||
"family_coverage": [],
|
||||
"model_calls": 0,
|
||||
"partitions": [],
|
||||
"query_seconds_by_kind": {},
|
||||
"question_outcomes": [],
|
||||
"regions_scanned": 0,
|
||||
"regions_unasked": 0,
|
||||
"regions_unjudged": 0,
|
||||
"scope": null,
|
||||
"seconds": 0.0,
|
||||
"skipped": "cpg_unavailable",
|
||||
"unjudged_sample": []
|
||||
},
|
||||
"obligations": {
|
||||
"degradations": [
|
||||
{
|
||||
"detail": "no path records; obligations evaluated within entry-point handlers only",
|
||||
"files": [
|
||||
"app.py",
|
||||
"safe.py"
|
||||
],
|
||||
"language": "python",
|
||||
"reason": "obligations_function_local",
|
||||
"stage": "obligations"
|
||||
},
|
||||
{
|
||||
"detail": "no hunter client or model",
|
||||
"reason": "intent_adjudication_unavailable",
|
||||
"stage": "obligations"
|
||||
}
|
||||
],
|
||||
"findings_by_label": {
|
||||
"function_local": 3
|
||||
},
|
||||
"operations": 4,
|
||||
"policy_version": null,
|
||||
"sets": [
|
||||
{
|
||||
"handlers": 1,
|
||||
"module": "app",
|
||||
"resource": "reports"
|
||||
},
|
||||
{
|
||||
"handlers": 1,
|
||||
"module": "app",
|
||||
"resource": "users"
|
||||
}
|
||||
],
|
||||
"sibling_sets": 2,
|
||||
"under_populated": 2
|
||||
},
|
||||
"provenance": {
|
||||
"agent_commits": 0,
|
||||
"sampled_commits": 0,
|
||||
"signals": [
|
||||
"git:unavailable"
|
||||
],
|
||||
"value": "human"
|
||||
},
|
||||
"score": {
|
||||
"by_category": {
|
||||
"Code Injection": 2,
|
||||
"Code Quality": 1,
|
||||
"Command or Argument Injection": 2,
|
||||
"Cryptographic Issues": 1,
|
||||
"Directory Traversal": 1,
|
||||
"Encapsulation": 2,
|
||||
"Information Leakage": 1,
|
||||
"SQL Injection": 2
|
||||
},
|
||||
"gate": {
|
||||
"block_severity_reachable": 5,
|
||||
"blocking": false,
|
||||
"min_score": 80,
|
||||
"passed": false
|
||||
},
|
||||
"max_severity": 5,
|
||||
"out_of_scope_dynamic_only": 0,
|
||||
"penalty_total": 105.6,
|
||||
"project_score": 17,
|
||||
"unmapped_cwe": []
|
||||
},
|
||||
"stages": {
|
||||
"completed": [
|
||||
"static",
|
||||
"map"
|
||||
],
|
||||
"requested": [
|
||||
"static",
|
||||
"map"
|
||||
],
|
||||
"skipped": []
|
||||
},
|
||||
"target": "<ROOT>/benchmarks/fixtures/python-vulnerable"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
scan_id=<RUN_ID>
|
||||
run_dir=<ROOT>/benchmarks/fixtures/python-vulnerable/.openultrasast/runs/<RUN_ID>
|
||||
file_targets=2
|
||||
ranked_targets=2
|
||||
findings=17
|
||||
calibrations_applied=1
|
||||
@@ -0,0 +1,280 @@
|
||||
{
|
||||
"verifications": [
|
||||
{
|
||||
"context_sources": [
|
||||
"evidence_level",
|
||||
"finding_id",
|
||||
"function_name",
|
||||
"line",
|
||||
"path",
|
||||
"reachability_conditions",
|
||||
"reachability_evidence",
|
||||
"reachability_status",
|
||||
"severity",
|
||||
"tags",
|
||||
"title"
|
||||
],
|
||||
"counter_case": "No function-level attacker-reachable entry point currently reaches this line.",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-flask-debug:app.py:81",
|
||||
"pro_case": "A static pattern matched the source line.",
|
||||
"required_next_step": "Add reachability evidence before prioritizing a fix.",
|
||||
"status": "rejected",
|
||||
"tie_breaker": "Provide call-graph, route, CLI, parser, or dynamic evidence linking attacker input to this function.",
|
||||
"verified": false
|
||||
},
|
||||
{
|
||||
"context_sources": [
|
||||
"evidence_level",
|
||||
"finding_id",
|
||||
"function_name",
|
||||
"line",
|
||||
"path",
|
||||
"reachability_conditions",
|
||||
"reachability_evidence",
|
||||
"reachability_status",
|
||||
"severity",
|
||||
"tags",
|
||||
"title"
|
||||
],
|
||||
"counter_case": "Impact and exploitability still require deeper verifier or dynamic evidence.",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-os-command:app.py:17",
|
||||
"pro_case": "Static corroboration exists and function-level reachability evidence is attached.",
|
||||
"required_next_step": "Proceed to standard verifier or dynamic reproduction if severity warrants it.",
|
||||
"status": "accepted",
|
||||
"tie_breaker": "Confirm attacker control, sanitizer absence, and impact in the next verification depth.",
|
||||
"verified": true
|
||||
},
|
||||
{
|
||||
"context_sources": [
|
||||
"evidence_level",
|
||||
"finding_id",
|
||||
"function_name",
|
||||
"line",
|
||||
"path",
|
||||
"reachability_conditions",
|
||||
"reachability_evidence",
|
||||
"reachability_status",
|
||||
"severity",
|
||||
"tags",
|
||||
"title"
|
||||
],
|
||||
"counter_case": "Impact and exploitability still require deeper verifier or dynamic evidence.",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-path-traversal:app.py:55",
|
||||
"pro_case": "Static corroboration exists and function-level reachability evidence is attached.",
|
||||
"required_next_step": "Proceed to standard verifier or dynamic reproduction if severity warrants it.",
|
||||
"status": "accepted",
|
||||
"tie_breaker": "Confirm attacker control, sanitizer absence, and impact in the next verification depth.",
|
||||
"verified": true
|
||||
},
|
||||
{
|
||||
"context_sources": [
|
||||
"evidence_level",
|
||||
"finding_id",
|
||||
"function_name",
|
||||
"line",
|
||||
"path",
|
||||
"reachability_conditions",
|
||||
"reachability_evidence",
|
||||
"reachability_status",
|
||||
"severity",
|
||||
"tags",
|
||||
"title"
|
||||
],
|
||||
"counter_case": "Impact and exploitability still require deeper verifier or dynamic evidence.",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-shell-true:app.py:23",
|
||||
"pro_case": "Static corroboration exists and function-level reachability evidence is attached.",
|
||||
"required_next_step": "Proceed to standard verifier or dynamic reproduction if severity warrants it.",
|
||||
"status": "accepted",
|
||||
"tie_breaker": "Confirm attacker control, sanitizer absence, and impact in the next verification depth.",
|
||||
"verified": true
|
||||
},
|
||||
{
|
||||
"context_sources": [
|
||||
"evidence_level",
|
||||
"finding_id",
|
||||
"function_name",
|
||||
"line",
|
||||
"path",
|
||||
"reachability_conditions",
|
||||
"reachability_evidence",
|
||||
"reachability_status",
|
||||
"severity",
|
||||
"tags",
|
||||
"title"
|
||||
],
|
||||
"counter_case": "Impact and exploitability still require deeper verifier or dynamic evidence.",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-sql-injection:app.py:44",
|
||||
"pro_case": "Static corroboration exists and function-level reachability evidence is attached.",
|
||||
"required_next_step": "Proceed to standard verifier or dynamic reproduction if severity warrants it.",
|
||||
"status": "accepted",
|
||||
"tie_breaker": "Confirm attacker control, sanitizer absence, and impact in the next verification depth.",
|
||||
"verified": true
|
||||
},
|
||||
{
|
||||
"context_sources": [
|
||||
"evidence_level",
|
||||
"finding_id",
|
||||
"function_name",
|
||||
"line",
|
||||
"path",
|
||||
"reachability_conditions",
|
||||
"reachability_evidence",
|
||||
"reachability_status",
|
||||
"severity",
|
||||
"tags",
|
||||
"title"
|
||||
],
|
||||
"counter_case": "Impact and exploitability still require deeper verifier or dynamic evidence.",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-sql-injection:app.py:50",
|
||||
"pro_case": "Static corroboration exists and function-level reachability evidence is attached.",
|
||||
"required_next_step": "Proceed to standard verifier or dynamic reproduction if severity warrants it.",
|
||||
"status": "accepted",
|
||||
"tie_breaker": "Confirm attacker control, sanitizer absence, and impact in the next verification depth.",
|
||||
"verified": true
|
||||
},
|
||||
{
|
||||
"context_sources": [
|
||||
"evidence_level",
|
||||
"finding_id",
|
||||
"function_name",
|
||||
"line",
|
||||
"path",
|
||||
"reachability_conditions",
|
||||
"reachability_evidence",
|
||||
"reachability_status",
|
||||
"severity",
|
||||
"tags",
|
||||
"title"
|
||||
],
|
||||
"counter_case": "Impact and exploitability still require deeper verifier or dynamic evidence.",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-ssrf:app.py:60",
|
||||
"pro_case": "Static corroboration exists and function-level reachability evidence is attached.",
|
||||
"required_next_step": "Proceed to standard verifier or dynamic reproduction if severity warrants it.",
|
||||
"status": "accepted",
|
||||
"tie_breaker": "Confirm attacker control, sanitizer absence, and impact in the next verification depth.",
|
||||
"verified": true
|
||||
},
|
||||
{
|
||||
"context_sources": [
|
||||
"evidence_level",
|
||||
"finding_id",
|
||||
"function_name",
|
||||
"line",
|
||||
"path",
|
||||
"reachability_conditions",
|
||||
"reachability_evidence",
|
||||
"reachability_status",
|
||||
"severity",
|
||||
"tags",
|
||||
"title"
|
||||
],
|
||||
"counter_case": "Impact and exploitability still require deeper verifier or dynamic evidence.",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-ssti:app.py:70",
|
||||
"pro_case": "Static corroboration exists and function-level reachability evidence is attached.",
|
||||
"required_next_step": "Proceed to standard verifier or dynamic reproduction if severity warrants it.",
|
||||
"status": "accepted",
|
||||
"tie_breaker": "Confirm attacker control, sanitizer absence, and impact in the next verification depth.",
|
||||
"verified": true
|
||||
},
|
||||
{
|
||||
"context_sources": [
|
||||
"evidence_level",
|
||||
"finding_id",
|
||||
"function_name",
|
||||
"line",
|
||||
"path",
|
||||
"reachability_conditions",
|
||||
"reachability_evidence",
|
||||
"reachability_status",
|
||||
"severity",
|
||||
"tags",
|
||||
"title"
|
||||
],
|
||||
"counter_case": "Impact and exploitability still require deeper verifier or dynamic evidence.",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-unsafe-deserialization:app.py:33",
|
||||
"pro_case": "Static corroboration exists and function-level reachability evidence is attached.",
|
||||
"required_next_step": "Proceed to standard verifier or dynamic reproduction if severity warrants it.",
|
||||
"status": "accepted",
|
||||
"tie_breaker": "Confirm attacker control, sanitizer absence, and impact in the next verification depth.",
|
||||
"verified": true
|
||||
},
|
||||
{
|
||||
"context_sources": [
|
||||
"evidence_level",
|
||||
"finding_id",
|
||||
"function_name",
|
||||
"line",
|
||||
"path",
|
||||
"reachability_conditions",
|
||||
"reachability_evidence",
|
||||
"reachability_status",
|
||||
"severity",
|
||||
"tags",
|
||||
"title"
|
||||
],
|
||||
"counter_case": "Impact and exploitability still require deeper verifier or dynamic evidence.",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-unsafe-deserialization:app.py:38",
|
||||
"pro_case": "Static corroboration exists and function-level reachability evidence is attached.",
|
||||
"required_next_step": "Proceed to standard verifier or dynamic reproduction if severity warrants it.",
|
||||
"status": "accepted",
|
||||
"tie_breaker": "Confirm attacker control, sanitizer absence, and impact in the next verification depth.",
|
||||
"verified": true
|
||||
},
|
||||
{
|
||||
"context_sources": [
|
||||
"evidence_level",
|
||||
"finding_id",
|
||||
"function_name",
|
||||
"line",
|
||||
"path",
|
||||
"reachability_conditions",
|
||||
"reachability_evidence",
|
||||
"reachability_status",
|
||||
"severity",
|
||||
"tags",
|
||||
"title"
|
||||
],
|
||||
"counter_case": "Impact and exploitability still require deeper verifier or dynamic evidence.",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-unsafe-eval:app.py:28",
|
||||
"pro_case": "Static corroboration exists and function-level reachability evidence is attached.",
|
||||
"required_next_step": "Proceed to standard verifier or dynamic reproduction if severity warrants it.",
|
||||
"status": "accepted",
|
||||
"tie_breaker": "Confirm attacker control, sanitizer absence, and impact in the next verification depth.",
|
||||
"verified": true
|
||||
},
|
||||
{
|
||||
"context_sources": [
|
||||
"evidence_level",
|
||||
"finding_id",
|
||||
"function_name",
|
||||
"line",
|
||||
"path",
|
||||
"reachability_conditions",
|
||||
"reachability_evidence",
|
||||
"reachability_status",
|
||||
"severity",
|
||||
"tags",
|
||||
"title"
|
||||
],
|
||||
"counter_case": "Impact and exploitability still require deeper verifier or dynamic evidence.",
|
||||
"evidence_level": "static_corroboration",
|
||||
"finding_id": "python-weak-hash:app.py:65",
|
||||
"pro_case": "Static corroboration exists and function-level reachability evidence is attached.",
|
||||
"required_next_step": "Proceed to standard verifier or dynamic reproduction if severity warrants it.",
|
||||
"status": "accepted",
|
||||
"tie_breaker": "Confirm attacker control, sanitizer absence, and impact in the next verification depth.",
|
||||
"verified": true
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,65 @@
|
||||
"""The equality instrument (harnessx-removal design section 3) sees a difference when there is one.
|
||||
|
||||
A comparison that always says "equal" would prove nothing, so these tests feed it records that differ in one
|
||||
place each and require it to name that place, and records that differ only in excluded fields and require it
|
||||
to pass.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
_SPEC = importlib.util.spec_from_file_location("hxr_equality", ROOT / "benchmarks" / "harnessx_removal_equality.py")
|
||||
assert _SPEC is not None and _SPEC.loader is not None
|
||||
equality = importlib.util.module_from_spec(_SPEC)
|
||||
_SPEC.loader.exec_module(equality)
|
||||
|
||||
|
||||
def _record(path: Path, *, findings: object, suite: dict[str, str], stdout: str = "findings=3\n") -> Path:
|
||||
(path / "scan").mkdir(parents=True)
|
||||
(path / "suite.json").write_text(equality.dump(suite))
|
||||
(path / "scan" / "findings.json").write_text(equality.dump(findings))
|
||||
(path / "scan" / "stdout.txt").write_text(stdout)
|
||||
(path / "env.json").write_text(json.dumps({"tree_ish": str(path)}))
|
||||
return path
|
||||
|
||||
|
||||
def _normalized(raw: dict[str, object], root: str) -> object:
|
||||
return equality.normalize(raw, root, "/tmp/x")
|
||||
|
||||
|
||||
def test_excluded_fields_and_run_ids_do_not_count(tmp_path: Path) -> None:
|
||||
a = _normalized({"rule": "sqli", "runtime_seconds": 1.2, "path": "/r1/runs/20260930T101010Z-0123abcd/f.py", "t_ms": 4}, "/r1")
|
||||
b = _normalized({"rule": "sqli", "runtime_seconds": 9.9, "path": "/r2/runs/20261001T111111Z-ffffffff/f.py", "t_ms": 7}, "/r2")
|
||||
suite = {"tests.test_x::test_a": "passed"}
|
||||
diffs = equality.compare(_record(tmp_path / "a", findings=a, suite=suite), _record(tmp_path / "b", findings=b, suite=suite), {})
|
||||
assert diffs == []
|
||||
|
||||
|
||||
def test_a_changed_finding_is_reported_with_its_key(tmp_path: Path) -> None:
|
||||
suite = {"tests.test_x::test_a": "passed"}
|
||||
a = _record(tmp_path / "a", findings=[{"rule": "sqli", "line": 3}], suite=suite)
|
||||
b = _record(tmp_path / "b", findings=[{"rule": "sqli", "line": 4}], suite=suite)
|
||||
assert equality.compare(a, b, {}) == ["scan/findings.json: $[0].line: 3 -> 4"]
|
||||
assert equality.main(["compare", str(a), str(b)]) == 1
|
||||
|
||||
|
||||
def test_a_changed_outcome_or_unmapped_test_is_reported(tmp_path: Path) -> None:
|
||||
a = _record(tmp_path / "a", findings=[], suite={"t::a": "passed", "t::gone": "passed", "t::old": "skipped"})
|
||||
b = _record(tmp_path / "b", findings=[], suite={"t::a": "failure", "t::new": "skipped", "t::extra": "passed"}, stdout="findings=2\n")
|
||||
diffs = equality.compare(a, b, {"deleted": ["t::gone"], "renamed": {"t::old": "t::new"}})
|
||||
assert diffs == [
|
||||
"suite.json: t::a passed -> failure",
|
||||
"suite.json: t::extra new in candidate",
|
||||
"scan/stdout.txt: first differing line 1: 'findings=3' -> 'findings=2'",
|
||||
]
|
||||
|
||||
|
||||
def test_a_file_present_on_one_side_only_is_reported(tmp_path: Path) -> None:
|
||||
a = _record(tmp_path / "a", findings=[], suite={})
|
||||
b = _record(tmp_path / "b", findings=[], suite={})
|
||||
(b / "scan" / "fusion.json").write_text("[]\n")
|
||||
assert equality.compare(a, b, {}) == ["scan/fusion.json: present only in candidate"]
|
||||
Reference in New Issue
Block a user