mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 11:49:28 +00:00
Eight of the ten vendored access-control rows had no named handler at all, so every one of them would have scored `missing_context` forever rather than as a detector miss. After the re-harvest nine are named and the tenth states its reason. The handler-context mode now carries the class its framework registers, multi-line decorators, and commented-out guards -- `#@token_required` above a ThreatByte handler is the entire bug. Where the registration lives in another document (connexion's `operationId`, an express router), a recipe names `context = [...]`, the harvester writes that stanza per side, and the pair materializes both files at their repository-relative paths so the entry-point mapper sees the layout the project has. `harvest_recipe` fetches every blob before the first write and the driver takes `--report`, so a failed fetch leaves a reviewed excerpt exactly as it was. Two defects the re-harvest exposed: `.mjs` was not a known suffix, so the one pair vendored as an ES module had no file target at all; and `_python_route_access` matched auth words inside decorator prose, reading `@ns.doc(description='... without proper authorization checks')` as a guard. Recorded in the hygiene artifact: nine of the ten duplicate groups sit on both sides of the split, which the repository-level split cannot see. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
233 lines
11 KiB
Python
233 lines
11 KiB
Python
"""learning-harness task 1.3: additive corpus fields and hygiene at load (offline)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
from openultrasast.pairs import DEFAULT_CATALOG, PairCase, load_pair_catalog, select_vendored
|
|
|
|
IDENTICAL_TWINS = {
|
|
"dsvw-dsvw-do-get",
|
|
"python-insecure-app-main-try-hack-me",
|
|
"pythonssti-main-read-root",
|
|
"vampi-books-get-by-title",
|
|
"vulnpy-deserialization-do-pickle-load",
|
|
}
|
|
VENDORED_PER_SLICE = {"local": 3, "github": 6, "sast": 11, "vibe-py": 35, "vfc-js": 17, "vfc": 176, "agent-vfc": 29}
|
|
|
|
|
|
def test_identical_twins_are_unscorable_and_no_row_is_dropped() -> None:
|
|
cases = load_pair_catalog(DEFAULT_CATALOG)
|
|
per_slice: dict[str, int] = {}
|
|
for case in select_vendored(cases):
|
|
per_slice[case.slice] = per_slice.get(case.slice, 0) + 1
|
|
assert per_slice == VENDORED_PER_SLICE # nothing dropped by the new accounting
|
|
twins = {case.name for case in cases if case.unscorable == "identical_twin"}
|
|
assert twins == IDENTICAL_TWINS
|
|
assert all(case.unscorable is None for case in cases if case.name not in IDENTICAL_TWINS and not case.known_limit)
|
|
|
|
|
|
def test_a_declared_known_limit_wins_over_a_computed_reason(tmp_path: Path) -> None:
|
|
from openultrasast.pairs import _load_catalog_file
|
|
|
|
(tmp_path / "v.py").write_text("# provenance: x\n\ndef f():\n return 1\n")
|
|
(tmp_path / "f.py").write_text("# provenance: y\n\ndef f():\n return 1\n")
|
|
head = (
|
|
'[[pair]]\nname = "t"\nslice = "sast"\nvuln = "v.py"\nfixed = "f.py"\nrelpath = "app.py"\n{extra}\n'
|
|
'[[pair.expected]]\ncwe = "CWE-95"\nclass = "x"\npath = "app.py"\nsink = "eval"\nmechanism = "other"\n'
|
|
)
|
|
(tmp_path / "plain.toml").write_text(head.format(extra=""))
|
|
(case,) = _load_catalog_file(tmp_path / "plain.toml")
|
|
assert case.unscorable == "identical_twin" # the provenance headers differ, the bodies do not
|
|
(tmp_path / "limited.toml").write_text(head.format(extra='known_limit = "java hash"'))
|
|
(limited,) = _load_catalog_file(tmp_path / "limited.toml")
|
|
assert limited.unscorable == "known_limit:java hash"
|
|
|
|
|
|
def test_expected_rows_carry_an_opaque_family_label(tmp_path: Path) -> None:
|
|
from openultrasast.pairs import _load_catalog_file
|
|
|
|
(tmp_path / "v.py").write_text("x = 1\n")
|
|
(tmp_path / "f.py").write_text("x = 2\n")
|
|
row = (
|
|
'[[pair]]\nname = "t"\nslice = "sast"\nvuln = "v.py"\nfixed = "f.py"\nrelpath = "app.py"\n\n'
|
|
'[[pair.expected]]\ncwe = "CWE-95"\nclass = "x"\npath = "app.py"\nsink = "eval"\nmechanism = "other"\nfamily = "{family}"\n'
|
|
)
|
|
(tmp_path / "c.toml").write_text(row.format(family="injection"))
|
|
(case,) = _load_catalog_file(tmp_path / "c.toml")
|
|
assert case.expected[0].family == "injection"
|
|
# the corpus stores the label; the classifier owns the taxonomy, so a fabricated id is not rejected here
|
|
(tmp_path / "d.toml").write_text(row.format(family="not_a_family"))
|
|
(other,) = _load_catalog_file(tmp_path / "d.toml")
|
|
assert other.expected[0].family == "not_a_family"
|
|
|
|
|
|
def test_duplicate_bodies_across_pairs_are_reported_before_any_split() -> None:
|
|
from openultrasast.pairs import duplicate_groups
|
|
|
|
groups = duplicate_groups(select_vendored(load_pair_catalog(DEFAULT_CATALOG)))
|
|
assert all(len(names) > 1 for names in groups)
|
|
assert all(len(set(names)) == len(names) for names in groups)
|
|
report = Path("benchmarks/measurements/2026-09-06-corpus-hygiene.json")
|
|
assert report.is_file(), "the hygiene report is committed beside the numbers it explains"
|
|
payload = json.loads(report.read_text())
|
|
assert payload["duplicate_groups"] == [list(names) for names in groups]
|
|
assert set(payload["identical_twins"]) == IDENTICAL_TWINS
|
|
assert groups == tuple(sorted(groups)), "the committed report must not reshuffle when one excerpt is re-harvested"
|
|
|
|
|
|
def test_duplicate_groups_that_cross_the_split_are_named_as_the_leak_they_are() -> None:
|
|
"""Two byte-identical excerpts on opposite sides of the split teach the holdout row exactly.
|
|
|
|
`split_by_repository` cannot see this: the duplicates are different repositories (a fork of a project, or one CVE
|
|
harvested under two names), so the repository-level split reports no straddle and the leak stays invisible."""
|
|
from openultrasast.pairs import duplicate_groups
|
|
|
|
cases = select_vendored(load_pair_catalog(DEFAULT_CATALOG))
|
|
splits = {case.name: case.split for case in cases}
|
|
crossing = [list(names) for names in duplicate_groups(cases) if len({splits[name] for name in names}) > 1]
|
|
payload = json.loads(Path("benchmarks/measurements/2026-09-06-corpus-hygiene.json").read_text())
|
|
assert payload["cross_split_duplicates"] == crossing
|
|
assert len(crossing) == 9, "nine of the ten duplicate groups cross the split; the number is the finding"
|
|
assert ["openniw-jobs-0fc947", "openniw-jobs-b74de3"] in crossing # two forks of one project, train and holdout
|
|
|
|
|
|
def test_split_by_repository_keeps_declared_splits_and_reports_straddles() -> None:
|
|
from openultrasast.pairs import split_by_repository
|
|
|
|
cases = select_vendored(load_pair_catalog(DEFAULT_CATALOG))
|
|
assigned, report = split_by_repository(cases)
|
|
declared = {case.name: case.split for case in cases if case.split_declared}
|
|
assert {case.name: case.split for case in assigned if case.name in declared} == declared # never re-splits a declared row
|
|
assert {name for name, _ in report.assigned} == {case.name for case in cases if not case.split_declared}
|
|
assert report.straddling, "repositories that sit in both splits are a corpus defect worth naming"
|
|
payload = json.loads(Path("benchmarks/measurements/2026-09-06-corpus-hygiene.json").read_text())
|
|
assert payload["straddling"] == [[repo, list(names)] for repo, names in report.straddling]
|
|
assert payload["undeclared_split"] == [list(item) for item in report.assigned]
|
|
|
|
|
|
def test_split_by_repository_assigns_undeclared_rows_by_repository_and_date(tmp_path: Path) -> None:
|
|
from openultrasast.pairs import split_by_repository
|
|
|
|
def case(name: str, repo: str, date: str = "") -> PairCase:
|
|
(tmp_path / f"{name}.py").write_text(f"# {name}\n")
|
|
return PairCase(
|
|
name=name,
|
|
slice="sast",
|
|
language="python",
|
|
origin="test",
|
|
vuln_file=tmp_path / f"{name}.py",
|
|
fixed_file=tmp_path / f"{name}.py",
|
|
relpath="app.py",
|
|
expected=(),
|
|
min_recall=1.0,
|
|
fix_policy="silent",
|
|
repo=repo,
|
|
split="",
|
|
split_declared=False,
|
|
fix_date=date,
|
|
)
|
|
|
|
cases = [case("a", "one", "2024-01-01"), case("b", "one", "2026-01-01"), case("c", "two", "2025-01-01"), case("d", "two", "2025-06-01")]
|
|
assigned, report = split_by_repository(cases, holdout_fraction=0.5)
|
|
by_name = {item.name: item.split for item in assigned}
|
|
assert by_name["a"] == by_name["b"] and by_name["c"] == by_name["d"] # a repository never straddles
|
|
assert {by_name["a"], by_name["c"]} == {"train", "holdout"}
|
|
assert by_name["a"] == "train" # oldest repository trains, newest is held out
|
|
assert dict(report.assigned) == by_name and report.straddling == () and report.undated == 0
|
|
|
|
|
|
def test_pair_signals_can_be_filtered_to_one_split() -> None:
|
|
from openultrasast.pairs import PairOutcome, build_pair_signals
|
|
|
|
def outcome(name: str, split: str) -> PairOutcome:
|
|
return PairOutcome(
|
|
name=name,
|
|
slice="sast",
|
|
language="python",
|
|
origin="t",
|
|
detected_vuln=False,
|
|
silent_fix=True,
|
|
pair_correct=False,
|
|
vuln_matched=0,
|
|
vuln_expected=1,
|
|
vuln_findings=0,
|
|
fix_findings=0,
|
|
fix_leaks=0,
|
|
recall=0.0,
|
|
misses=("r:app.py:CWE-95",),
|
|
leaks=(),
|
|
split=split,
|
|
)
|
|
|
|
outcomes = [outcome("train-one", "train"), outcome("holdout-one", "holdout")]
|
|
assert {signal["pair"] for signal in build_pair_signals(outcomes)} == {"train-one", "holdout-one"}
|
|
assert {signal["pair"] for signal in build_pair_signals(outcomes, split="train")} == {"train-one"}
|
|
|
|
|
|
HEADER_C = "/* Provenance: acme/lib bad ({side}).\n * repo: acme/lib\n * commit: abc\n * license: MIT\n */\n"
|
|
HEADER_PY = "# Provenance: acme/lib f ({side}).\n# repo: acme/lib\n# license: MIT\n"
|
|
|
|
|
|
def _twin(tmp_path: Path, suffix: str, vuln: str, fixed: str) -> object:
|
|
from openultrasast.pairs import _load_catalog_file
|
|
|
|
(tmp_path / f"v{suffix}").write_text(vuln)
|
|
(tmp_path / f"f{suffix}").write_text(fixed)
|
|
(tmp_path / "c.toml").write_text(
|
|
f'[[pair]]\nname = "t"\nslice = "sast"\nvuln = "v{suffix}"\nfixed = "f{suffix}"\nrelpath = "app{suffix}"\n\n'
|
|
'[[pair.expected]]\ncwe = "CWE-121"\nclass = "x"\npath = "app.c"\nsink = "strcpy"\nmechanism = "other"\n'
|
|
)
|
|
(case,) = _load_catalog_file(tmp_path / "c.toml")
|
|
return case
|
|
|
|
|
|
def test_a_preprocessor_directive_is_code_not_a_provenance_header(tmp_path: Path) -> None:
|
|
"""The whole point of the CWE-121 pairs is the buffer size, and it lives on a `#define`."""
|
|
body = "#include <string.h>\n#define BUFSIZE {size}\n\nvoid bad(void) {{\n char buf[BUFSIZE];\n strcpy(buf, src);\n}}\n"
|
|
case = _twin(
|
|
tmp_path,
|
|
".c",
|
|
HEADER_C.format(side="vuln") + body.format(size="10"),
|
|
HEADER_C.format(side="fixed") + body.format(size="4096"),
|
|
)
|
|
assert case.unscorable is None # type: ignore[attr-defined]
|
|
|
|
|
|
def test_a_leading_code_comment_is_not_a_provenance_header(tmp_path: Path) -> None:
|
|
same = "def f(value):\n return value\n"
|
|
case = _twin(
|
|
tmp_path,
|
|
".py",
|
|
HEADER_PY.format(side="vuln") + "# this function is fine\n" + same,
|
|
HEADER_PY.format(side="fixed") + "# this function is not fine\n" + same,
|
|
)
|
|
assert case.unscorable is None # a comment that is not provenance metadata is part of the file
|
|
|
|
|
|
def test_the_provenance_header_itself_is_still_stripped(tmp_path: Path) -> None:
|
|
same = "def f(value):\n return value\n"
|
|
case = _twin(tmp_path, ".py", HEADER_PY.format(side="vuln") + same, HEADER_PY.format(side="fixed") + same)
|
|
assert case.unscorable == "identical_twin" # only the header differs, so the code is the same code
|
|
block = _twin(
|
|
tmp_path, ".c", HEADER_C.format(side="vuln") + "void bad(void) {}\n", HEADER_C.format(side="fixed") + "void bad(void) {}\n"
|
|
)
|
|
assert block.unscorable == "identical_twin" # the C block comment form too
|
|
|
|
|
|
def test_no_vendored_excerpt_loses_a_preprocessor_directive_to_the_header_strip() -> None:
|
|
from openultrasast.pairs import _header_lines
|
|
|
|
eaten: list[str] = []
|
|
for case in select_vendored(load_pair_catalog(DEFAULT_CATALOG)):
|
|
for path in (case.vuln_file, case.fixed_file):
|
|
if not path.is_file():
|
|
continue
|
|
lines = path.read_text(errors="ignore").splitlines()
|
|
stripped = lines[: _header_lines(lines)]
|
|
if any(re.match(r"\s*#\s*(include|define|if|ifdef|ifndef|elif|else|endif|pragma|undef|import)\b", line) for line in stripped):
|
|
eaten.append(f"{case.name}:{path.name}")
|
|
assert eaten == []
|