feat(real-world-vfc-slice): add labeled OpenSSL/Firefox/Chromium/curl pair corpus

Harvest 176 isolated vuln-vs-fix functions (109 CVEs, 352 training rows)
into benchmarks/pairs/vfc with provenance and a JSONL training manifest.
Score the slice as an honesty dashboard (ousast pairs --slice vfc);
keep the pair gate on the local fixtures only.
This commit is contained in:
Marius Ciepluch
2026-09-04 16:45:38 +02:00
parent e9f185da59
commit 0116f9bea6
372 changed files with 49209 additions and 49 deletions
+75 -1
View File
@@ -10,6 +10,7 @@ from openultrasast.pair_gate import pair_gate
from openultrasast.pairs import (
DEFAULT_CATALOG,
DEFAULT_SAST_CATALOG,
DEFAULT_VFC_CATALOG,
PairCase,
build_pair_signals,
evaluate_catalog,
@@ -24,12 +25,15 @@ def test_catalog_loads_local_and_github_slices() -> None:
cases = load_pair_catalog(DEFAULT_CATALOG)
slices = {case.slice for case in cases}
names = {case.name for case in cases}
assert {"local", "github", "sast"} <= slices
assert {"local", "github", "sast", "vfc"} <= slices
assert "local-python-vulnerable" in names
assert "sven-cwe-078-os-system" in names
assert "hutool-cve-2018-17297" in names
assert "owasp-python-codeinj" in names
assert "juliet-c-cwe134-printf" in names
assert "openssl-cve-2014-0160" in names
assert "firefox-cve-2020-15667" in names
assert "chromium-cve-2019-5786" in names
assert all(case.vuln_file.is_file() and case.fixed_file.is_file() for case in cases)
@@ -52,8 +56,13 @@ def test_datasets_catalog_lists_public_vfc_repos() -> None:
"OWASP Benchmark Python",
"Juliet Test Suite C/C++",
"Juliet Test Suite Java",
"OpenSSL",
"Firefox mozilla-central",
"Chromium",
"curl",
} <= names
assert DEFAULT_SAST_CATALOG.is_file()
assert DEFAULT_VFC_CATALOG.is_file()
java = Path("benchmarks/pairs/datasets/cwe-bench-java-project_info.csv")
assert java.is_file()
assert "CVE-2018-17297" in java.read_text()
@@ -161,6 +170,55 @@ def test_sast_slice_scores_owasp_and_juliet() -> None:
assert -1.0 <= result.overall.youden <= 1.0
def test_vfc_slice_is_labeled_openssl_firefox_chromium() -> None:
cases = select_slice(load_pair_catalog(), "vfc")
by_name = {case.name: case for case in cases}
assert set(by_name) >= {
"openssl-cve-2014-0160",
"firefox-cve-2020-15667",
"chromium-cve-2019-5786",
"openssl-cve-2022-1292",
"chromium-cve-2022-2162",
"curl-cve-2023-38545",
"openssl-cve-2025-69421",
}
for case in cases:
header = case.vuln_file.read_text() + case.fixed_file.read_text()
assert case.commit_url
assert case.cve
assert case.license
assert case.cve in header
assert case.license.split()[0] in header
assert case.vuln_file != case.fixed_file
sample = tuple(case for case in cases if case.name in {"openssl-cve-2014-0160", "firefox-cve-2020-15667", "chromium-cve-2019-5786"})
result = evaluate_catalog(sample)
assert result.overall.pairs == len(sample)
assert "overlay" in result.scorers["vfc"]
assert "inventory" in result.scorers["vfc"]
# Honesty dashboard: memcpy-after-fix and UAF are labeled, not a 100% gate.
assert -1.0 <= result.overall.youden <= 1.0
def test_vfc_training_manifest_has_vuln_and_fixed_labels() -> None:
import json
manifest = Path("benchmarks/pairs/vfc/training/manifest.jsonl")
rows = [json.loads(line) for line in manifest.read_text().splitlines() if line.strip()]
assert rows
assert {row["label"] for row in rows} == {"vuln", "fixed"}
by_pair: dict[str, set[str]] = {}
for row in rows:
by_pair.setdefault(str(row["pair"]), set()).add(str(row["label"]))
excerpt = Path("benchmarks/pairs/vfc") / str(row["file"])
assert excerpt.is_file(), row["id"]
assert 2014 <= int(row["year"]) <= 2026
assert all(labels == {"vuln", "fixed"} for labels in by_pair.values())
recent = {row["pair"] for row in rows if 2020 <= int(row["year"]) <= 2026}
assert len(recent) >= 80
assert len(by_pair) >= 100
assert len(rows) == 2 * len(by_pair)
def test_cli_pairs_json_scoreboard() -> None:
import io
from contextlib import redirect_stdout
@@ -171,3 +229,19 @@ def test_cli_pairs_json_scoreboard() -> None:
payload = json.loads(buf.getvalue())
assert payload["overall"]["pairs"] >= 4
assert "sven-cwe-078-os-system" in {item["name"] for item in payload["outcomes"]}
def test_cli_vfc_json_includes_dual_scorers() -> None:
import io
from contextlib import redirect_stdout
buf = io.StringIO()
with redirect_stdout(buf):
assert main(["pairs", "--slice", "vfc", "--json"]) == 0
payload = json.loads(buf.getvalue())
names = {item["name"] for item in payload["outcomes"]}
assert {"openssl-cve-2014-0160", "firefox-cve-2020-15667", "chromium-cve-2019-5786"} <= names
assert payload["overall"]["pairs"] >= 3
assert "overlay" in payload["scorers"]["vfc"]
assert "inventory" in payload["scorers"]["vfc"]
assert payload["per_slice"]["vfc"]["pairs"] == payload["scorers"]["vfc"]["overlay"]["pairs"]
+95
View File
@@ -0,0 +1,95 @@
"""Maintainer VFC harvest stays offline in CI: extract and reject, no HTTP."""
from __future__ import annotations
import runpy
from pathlib import Path
import pytest
HARVEST = Path("benchmarks/pairs/vfc/harvest.py")
def _harvest() -> dict[str, object]:
return runpy.run_path(str(HARVEST))
def test_extract_function_round_trips_perl_sub_without_parens() -> None:
harvest = _harvest()
source = """
sub other { return 0; }
sub link_hash_cert {
my ($hash, $fprint) = `"$openssl" x509 -in "$fname"`;
}
sub later { return 1; }
"""
body = harvest["extract_function"](source, "link_hash_cert")
assert "sub link_hash_cert {" in body
assert "`\"$openssl\" x509" in body
assert "sub other" not in body
assert "sub later" not in body
def test_extract_function_round_trips_a_local_body() -> None:
harvest = _harvest()
source = """
int before(void) { return 0; }
int target_fn(char *name, int namelen) {
memcpy(name, name, namelen + 1);
return namelen;
}
int after(void) { return 1; }
"""
body = harvest["extract_function"](source, "target_fn")
assert "int target_fn(char *name, int namelen)" in body
assert "memcpy(name, name, namelen + 1);" in body
assert "int before" not in body
assert "int after" not in body
def test_extract_function_skips_prototype_before_definition() -> None:
harvest = _harvest()
source = """
class Loader {
public:
char *ArrayBufferResult();
};
char *Loader::ArrayBufferResult() {
return raw_data_->ToArrayBuffer();
}
"""
body = harvest["extract_function"](source, "ArrayBufferResult")
assert "Loader::ArrayBufferResult" in body
assert "return raw_data_->ToArrayBuffer();" in body
assert "class Loader" not in body
def test_validate_recipe_rejects_advisory_only_and_fixfox() -> None:
harvest = _harvest()
validate = harvest["validate_recipe"]
with pytest.raises(harvest["RecipeError"], match="advisory-only"):
validate({"name": "mfsa-only", "cve": "CVE-2019-11730"})
with pytest.raises(harvest["RecipeError"], match="embargoed"):
validate(
{
"name": "fixfox",
"parent": "abc",
"commit": "def",
"path": "a.c",
"function": "f",
"license": "MIT",
"url": "https://zenodo.org/record/fixfox",
}
)
def test_harvest_dry_run_accepts_seed_recipes() -> None:
harvest = _harvest()
recipes = {str(item["name"]): item for item in harvest["load_recipes"](Path("benchmarks/pairs/vfc/recipes.toml"))}
assert {"openssl-cve-2014-0160", "firefox-cve-2020-15667", "chromium-cve-2019-5786"} <= set(recipes)
for recipe in recipes.values():
harvest["validate_recipe"](recipe)
assert harvest["main"](["--name", "openssl-cve-2014-0160"]) == 0