2026-09-24 16:12:22 +02:00
|
|
|
"""The independent population is reserved, well formed, and untouched by anything in the development tree.
|
|
|
|
|
|
|
|
|
|
Every rule this project has learned was learned on its development subjects, so those can no longer measure
|
|
|
|
|
whether the rules generalise. This reservation is the population that can -- until something scans it. The
|
|
|
|
|
check below fails the moment a reserved repository is referenced by a recipe, catalog, manifest or measurement
|
|
|
|
|
anywhere else, which is how an evaluation set quietly becomes a development one.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2026-10-04 19:17:35 +02:00
|
|
|
import hashlib
|
|
|
|
|
import json
|
2026-09-24 16:12:22 +02:00
|
|
|
import re
|
|
|
|
|
import tomllib
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
2026-09-26 17:53:51 +02:00
|
|
|
import pytest
|
|
|
|
|
|
2026-09-24 16:12:22 +02:00
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
2026-09-26 17:53:51 +02:00
|
|
|
POPULATIONS = sorted((ROOT / "benchmarks" / "independent").glob("population-v*.toml"))
|
|
|
|
|
POPULATION = POPULATIONS[0]
|
2026-09-30 18:03:07 +02:00
|
|
|
# v1 and v2 score injection, untrusted_destination and config_secrets; v3 (PHP) adds the other four families that
|
|
|
|
|
# protocol-v3.md pre-registers matching rules for.
|
|
|
|
|
FAMILIES = {"injection", "untrusted_destination", "config_secrets", "path", "output_encoding", "access_control", "deserialization"}
|
2026-09-24 16:12:22 +02:00
|
|
|
SHA = re.compile(r"[0-9a-f]{40}")
|
|
|
|
|
|
|
|
|
|
|
2026-09-26 17:53:51 +02:00
|
|
|
def _population(path: Path = POPULATION) -> dict:
|
|
|
|
|
return tomllib.loads(path.read_text())
|
2026-09-24 16:12:22 +02:00
|
|
|
|
|
|
|
|
|
2026-09-26 17:53:51 +02:00
|
|
|
@pytest.mark.parametrize("path", POPULATIONS, ids=lambda p: p.stem)
|
|
|
|
|
def test_the_reservation_is_well_formed(path: Path) -> None:
|
|
|
|
|
data = _population(path)
|
2026-09-30 18:03:07 +02:00
|
|
|
assert data["status"] in {"reserved-unscanned", "reserved", "frozen"}
|
2026-09-24 16:12:22 +02:00
|
|
|
cases = data["case"]
|
|
|
|
|
ids = [case["id"] for case in cases]
|
|
|
|
|
assert len(ids) == len(set(ids)) >= 10
|
|
|
|
|
for case in cases:
|
|
|
|
|
assert case["family"] in FAMILIES, case["id"]
|
|
|
|
|
assert SHA.fullmatch(case["vulnerable"]) and SHA.fullmatch(case["fixed"]), case["id"]
|
|
|
|
|
assert case["vulnerable"] != case["fixed"], case["id"]
|
|
|
|
|
assert case["repo"].startswith("https://github.com/"), case["id"]
|
|
|
|
|
assert case["advisory"] and case["sink"] and case["privilege"], case["id"]
|
2026-09-24 16:43:32 +02:00
|
|
|
if data["status"] == "frozen":
|
|
|
|
|
# Frozen means every case carries its benign control: a change from the same history, reviewed as not a
|
|
|
|
|
# security change, whose two sides a correct analyzer must not tell apart.
|
|
|
|
|
for case in cases:
|
|
|
|
|
benign = case.get("benign") or {}
|
|
|
|
|
assert SHA.fullmatch(benign.get("base", "")) and SHA.fullmatch(benign.get("tip", "")), case["id"]
|
2026-09-26 17:53:51 +02:00
|
|
|
assert (path.parent / data["freeze_record"]).is_file()
|
2026-09-24 16:12:22 +02:00
|
|
|
languages = {case["language"] for case in cases}
|
2026-09-30 18:03:07 +02:00
|
|
|
if "language" in data:
|
|
|
|
|
# A single-language population (v3: PHP) declares its language, and every case is in it.
|
|
|
|
|
assert languages == {data["language"]}
|
|
|
|
|
else:
|
|
|
|
|
assert {"php", "python"} <= languages and languages & {"javascript", "typescript"}
|
2026-09-24 16:12:22 +02:00
|
|
|
|
|
|
|
|
|
2026-09-26 17:53:51 +02:00
|
|
|
@pytest.mark.parametrize("path", POPULATIONS, ids=lambda p: p.stem)
|
|
|
|
|
def test_a_multi_language_repository_is_reserved(path: Path) -> None:
|
2026-09-24 16:12:22 +02:00
|
|
|
"""The declared v0.1 target is a WordPress plugin: PHP plus its admin JavaScript (task 13.4)."""
|
2026-09-26 17:53:51 +02:00
|
|
|
data = _population(path)
|
2026-09-24 16:12:22 +02:00
|
|
|
by_id = {case["id"]: case for case in data["case"]}
|
|
|
|
|
members = data["multi_language_members"]
|
|
|
|
|
assert members and all(by_id[m]["language"] == "php" and "javascript" in by_id[m].get("also", []) for m in members)
|
|
|
|
|
|
|
|
|
|
|
2026-10-04 19:17:35 +02:00
|
|
|
def _pool_manifests() -> list[Path]:
|
|
|
|
|
directory = ROOT / "benchmarks/unseen"
|
|
|
|
|
return sorted([*directory.glob("*.toml"), *directory.glob("private/*.toml")])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _pool_names() -> set[str]:
|
|
|
|
|
names = set()
|
|
|
|
|
for path in _pool_manifests():
|
|
|
|
|
text = path.read_text().lower()
|
|
|
|
|
names.update(m.rstrip("/").removesuffix(".git") for m in re.findall(r"github\.com/([\w.-]+/[\w.-]+)", text))
|
|
|
|
|
return names
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _population_names() -> set[str]:
|
2026-09-26 17:53:51 +02:00
|
|
|
reserved = {case["repo"].rstrip("/").lower() for path in POPULATIONS for case in _population(path)["case"]}
|
2026-10-04 19:17:35 +02:00
|
|
|
return {url.rsplit("/", 2)[-2] + "/" + url.rsplit("/", 1)[-1] for url in reserved}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_no_reserved_repository_is_referenced_by_the_development_tree() -> None:
|
|
|
|
|
reservations = [(ROOT / "benchmarks/independent", _population_names()), (ROOT / "benchmarks/unseen", _pool_names())]
|
|
|
|
|
offenders = 0
|
|
|
|
|
counts = {}
|
|
|
|
|
for top in ("benchmarks", "src", "tests", "plane"):
|
2026-09-24 16:12:22 +02:00
|
|
|
for path in (ROOT / top).rglob("*"):
|
2026-10-04 19:17:35 +02:00
|
|
|
if not path.is_file() or path == Path(__file__) or path.suffix in {".pyc", ".bin", ".gz"}:
|
|
|
|
|
continue
|
|
|
|
|
# Preserve the population guard's existing scope; plane is newly reserved for pools.
|
|
|
|
|
active = [
|
|
|
|
|
(directory, names)
|
|
|
|
|
for directory, names in reservations
|
|
|
|
|
if directory not in path.parents and names and not (top == "plane" and directory == ROOT / "benchmarks/independent")
|
|
|
|
|
]
|
|
|
|
|
if not active:
|
2026-09-24 16:12:22 +02:00
|
|
|
continue
|
|
|
|
|
try:
|
|
|
|
|
text = path.read_text(errors="ignore").lower()
|
|
|
|
|
except OSError:
|
|
|
|
|
continue
|
2026-10-04 19:17:35 +02:00
|
|
|
count = sum(1 for _, names in active for name in names if f"github.com/{name}" in text)
|
|
|
|
|
offenders += count
|
|
|
|
|
counts[top] = counts.get(top, 0) + count
|
|
|
|
|
assert not offenders, f"reserved repository referenced outside its reservation: count={offenders}, sources={counts}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_pool_and_population_names_are_disjoint() -> None:
|
|
|
|
|
count = len(_population_names() & _pool_names())
|
|
|
|
|
assert not count, f"overlapping reservations: count={count}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def manifest_digest(path: Path) -> str:
|
|
|
|
|
"""Canonical parsed TOML, UTF-8 JSON with sorted keys and compact separators.
|
|
|
|
|
|
|
|
|
|
Private entries are covered by their opaque pointers' digests in the public manifest.
|
|
|
|
|
The freeze writer must use the same encoding (task 3.2).
|
|
|
|
|
"""
|
|
|
|
|
raw = json.dumps(tomllib.loads(path.read_text()), sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()
|
|
|
|
|
return hashlib.sha256(raw).hexdigest()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_pool_digest(path: Path) -> None:
|
|
|
|
|
record = path.with_name("freeze-" + path.stem.removeprefix("pool-") + ".json")
|
|
|
|
|
expected = json.loads(record.read_text())["freeze_digest"]
|
|
|
|
|
assert manifest_digest(path) == expected, "frozen pool digest mismatch"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_frozen_pool_manifest_digest() -> None:
|
|
|
|
|
pools = sorted((ROOT / "benchmarks/unseen").glob("pool-p*.toml"))
|
|
|
|
|
if not pools:
|
|
|
|
|
pytest.skip("no pool frozen")
|
|
|
|
|
for path in pools:
|
|
|
|
|
check_pool_digest(path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_pool_guard_controls(tmp_path, monkeypatch) -> None:
|
|
|
|
|
"""A synthetic reservation proves green, then red outside its own directory."""
|
|
|
|
|
population = tmp_path / "benchmarks/independent/population-v1.toml"
|
|
|
|
|
pool = tmp_path / "benchmarks/unseen/pool-p1.toml"
|
|
|
|
|
population.parent.mkdir(parents=True)
|
|
|
|
|
pool.parent.mkdir(parents=True)
|
|
|
|
|
prefix = "https://" + "github.com/"
|
|
|
|
|
population.write_text('[[case]]\nrepo = "' + prefix + 'synthetic/population"\n')
|
|
|
|
|
pool.write_text('[[repository]]\nrepo = "' + prefix + 'synthetic/pool"\n')
|
|
|
|
|
monkeypatch.setattr(__import__(__name__), "ROOT", tmp_path)
|
|
|
|
|
monkeypatch.setattr(__import__(__name__), "POPULATIONS", [population])
|
|
|
|
|
test_no_reserved_repository_is_referenced_by_the_development_tree()
|
|
|
|
|
leak = tmp_path / "benchmarks/measurements/fixture.txt"
|
|
|
|
|
leak.parent.mkdir(parents=True)
|
|
|
|
|
leak.write_text(prefix + "synthetic/pool")
|
|
|
|
|
with pytest.raises(AssertionError, match="count=1"):
|
|
|
|
|
test_no_reserved_repository_is_referenced_by_the_development_tree()
|
|
|
|
|
leak.unlink()
|
|
|
|
|
plane = tmp_path / "plane/fixture.txt"
|
|
|
|
|
plane.parent.mkdir()
|
|
|
|
|
plane.write_text(prefix + "synthetic/pool")
|
|
|
|
|
with pytest.raises(AssertionError, match="count=1"):
|
|
|
|
|
test_no_reserved_repository_is_referenced_by_the_development_tree()
|
|
|
|
|
plane.write_text(prefix + "synthetic/population")
|
|
|
|
|
test_no_reserved_repository_is_referenced_by_the_development_tree()
|
|
|
|
|
plane.unlink()
|
|
|
|
|
private = pool.parent / "private/draft-p1.toml"
|
|
|
|
|
private.parent.mkdir()
|
|
|
|
|
private.write_text('[[repository]]\nrepo = "' + prefix + 'synthetic/population"\n')
|
|
|
|
|
with pytest.raises(AssertionError, match="count=2"):
|
|
|
|
|
test_no_reserved_repository_is_referenced_by_the_development_tree()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_pool_digest_control(tmp_path) -> None:
|
|
|
|
|
pool = tmp_path / "pool-p1.toml"
|
|
|
|
|
pool.write_text('status = "frozen"\n[[repository]]\nid = "opaque"\n')
|
|
|
|
|
record = tmp_path / "freeze-p1.json"
|
|
|
|
|
record.write_text(json.dumps({"freeze_digest": manifest_digest(pool)}))
|
|
|
|
|
check_pool_digest(pool)
|
|
|
|
|
pool.write_text(pool.read_text() + "slice = 2\n")
|
|
|
|
|
with pytest.raises(AssertionError, match="digest"):
|
|
|
|
|
check_pool_digest(pool)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_frozen_pool_discovery_cannot_be_disabled(tmp_path, monkeypatch) -> None:
|
|
|
|
|
pool = tmp_path / "benchmarks/unseen/pool-p1.toml"
|
|
|
|
|
pool.parent.mkdir(parents=True)
|
|
|
|
|
pool.write_text('status = "frozen"\n')
|
|
|
|
|
record = pool.with_name("freeze-p1.json")
|
|
|
|
|
record.write_text(json.dumps({"freeze_digest": manifest_digest(pool)}))
|
|
|
|
|
monkeypatch.setattr(__import__(__name__), "ROOT", tmp_path)
|
|
|
|
|
test_frozen_pool_manifest_digest()
|
|
|
|
|
pool.write_text('status = "draft"\n')
|
|
|
|
|
with pytest.raises(AssertionError, match="digest"):
|
|
|
|
|
test_frozen_pool_manifest_digest()
|