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`,
|
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.
|
`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
|
- Equality script (design §3) with its `find_spec("harnessx")` and freeze checks; re-sync `.venv` without the
|
||||||
`harnessx` extra first.
|
`harnessx` extra first.
|
||||||
- Record `benchmarks/measurements/<date>-harnessx-removal-baseline/`: full suite, `ousast pairs --json`, the
|
- 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