mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 11:49:28 +00:00
Task 1.3. An expected row may carry a family label, stored opaquely because the classifier owns the taxonomy and the corpus module never imports the learning package. Every pair carries a computed reason when it cannot be scored: a declared known limit wins, otherwise identical twin when the two excerpts are the same code once the provenance header is stripped. No row is ever dropped. Two corpus defects the new accounting surfaces, committed as an artifact beside the numbers they cap. Nineteen repositories sit on both sides of the split, so a learning path could be taught by one file of a repository and evaluated on another. Ten groups of pairs share a vulnerable excerpt byte for byte, mostly the same fix harvested twice at different granularity. The split helper keeps every declared split, lets rows that named none inherit their repository's side, and otherwise assigns a whole repository at once, oldest first, so nothing straddles by our doing. Pair signals can now be restricted to one split, which is what lets a learning path teach from train rows only. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
150 lines
6.9 KiB
Python
150 lines
6.9 KiB
Python
"""learning-harness task 1.3: additive corpus fields and hygiene at load (offline)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
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
|
|
|
|
|
|
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"}
|