2026-09-08 14:45:12 +02:00
|
|
|
"""model-grounded-detection task 4.1: guard dominance, the arbiter for bugs that never crash (Req 6.2).
|
|
|
|
|
|
|
|
|
|
An access-control bug is the *absence* of a guard, so there is no flow to follow and no crash to reproduce.
|
|
|
|
|
What the model can establish is a consistency violation: an obligated operation that no discharging guard
|
|
|
|
|
dominates, in a file where its siblings are dominated by one. That asymmetry is the evidence.
|
|
|
|
|
|
|
|
|
|
This replaces the line-order heuristic in `semantic/obligations/dominance.py` ("the guard's line is before the
|
|
|
|
|
operation's line, with an early exit between") with real control-flow dominance over the CFG.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _spec(): # type: ignore[no-untyped-def]
|
|
|
|
|
from openultrasast.model.specs import DominanceSpec
|
|
|
|
|
|
|
|
|
|
return DominanceSpec(
|
|
|
|
|
family="access_control",
|
|
|
|
|
language="python",
|
|
|
|
|
operations=("filter_by", "query.get", "session.delete"),
|
|
|
|
|
dischargers=("login_required", "current_user", "check_permission"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _cpg(rows): # type: ignore[no-untyped-def]
|
|
|
|
|
from openultrasast.cpg.backend import CpgResult
|
|
|
|
|
|
|
|
|
|
return CpgResult(cpg_path=Path("cpg.bin"), run=lambda q, p: rows)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _op(method: str, guards: list[str], line: int = 10) -> dict[str, object]:
|
2026-09-08 19:46:58 +02:00
|
|
|
return {"operation": "Note.query.filter_by(id=nid)", "opLine": str(line), "opMethod": method, "dominatingGuards": guards}
|
2026-09-08 14:45:12 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_an_unguarded_operation_among_guarded_siblings_is_entailed() -> None:
|
|
|
|
|
"""The consistency violation: every sibling checks the owner, this one does not."""
|
|
|
|
|
from openultrasast.model.dominance import verdict
|
|
|
|
|
from openultrasast.model.ladder import Rung
|
|
|
|
|
|
|
|
|
|
rows = [_op("leaky", []), _op("list_notes", ["current_user"]), _op("edit_note", ["current_user"])]
|
|
|
|
|
answer = verdict(_cpg(rows), _spec(), function="leaky")
|
|
|
|
|
assert answer is not None and answer.rung is Rung.ENTAILED
|
|
|
|
|
assert answer.family == "access_control"
|
|
|
|
|
assert "leaky" in answer.witness and "2 sibling" in answer.witness, "the witness names the operation and its siblings"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_a_method_with_two_operations_counts_once_as_a_sibling() -> None:
|
|
|
|
|
"""One handler holding two obligated operations is one piece of corroboration, not two."""
|
|
|
|
|
from openultrasast.model.dominance import verdict
|
|
|
|
|
|
|
|
|
|
rows = [_op("leaky", []), _op("listing", ["current_user"], line=3), _op("listing", ["current_user"], line=4)]
|
|
|
|
|
answer = verdict(_cpg(rows), _spec(), function="leaky")
|
|
|
|
|
assert answer is not None and "1 sibling" in answer.witness
|
|
|
|
|
assert answer.witness.count("listing") == 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_a_guarded_operation_yields_no_finding() -> None:
|
|
|
|
|
from openultrasast.model.dominance import verdict
|
|
|
|
|
|
|
|
|
|
rows = [_op("safe", ["current_user"]), _op("other", ["current_user"])]
|
|
|
|
|
assert verdict(_cpg(rows), _spec(), function="safe") is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_an_unguarded_operation_with_no_guarded_sibling_is_only_corroborated() -> None:
|
|
|
|
|
"""Nothing to be inconsistent with: the whole module may be unauthenticated by design, and saying
|
|
|
|
|
`entailed` there would assert a policy the model cannot see."""
|
|
|
|
|
from openultrasast.model.dominance import verdict
|
|
|
|
|
from openultrasast.model.ladder import Rung
|
|
|
|
|
|
|
|
|
|
rows = [_op("a", []), _op("b", [])]
|
|
|
|
|
answer = verdict(_cpg(rows), _spec(), function="a")
|
|
|
|
|
assert answer is not None and answer.rung is Rung.CORROBORATED
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_no_obligated_operation_yields_no_verdict() -> None:
|
|
|
|
|
from openultrasast.model.dominance import verdict
|
|
|
|
|
|
|
|
|
|
assert verdict(_cpg([]), _spec(), function="whatever") is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_a_failed_query_is_not_read_as_an_absence_of_operations() -> None:
|
|
|
|
|
"""`None` from the engine means "could not decide", never "there is nothing here"."""
|
|
|
|
|
from openultrasast.cpg.backend import CpgResult
|
|
|
|
|
from openultrasast.model.dominance import verdict
|
|
|
|
|
|
|
|
|
|
broken = CpgResult(cpg_path=Path("c.bin"), run=lambda q, p: None)
|
|
|
|
|
assert verdict(broken, _spec(), function="a") is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_the_verdict_is_deterministic_over_one_cpg() -> None:
|
|
|
|
|
"""Req 6.4, for the absence arbiter as well as the flow one."""
|
|
|
|
|
from openultrasast.model.dominance import verdict
|
|
|
|
|
|
|
|
|
|
rows = [_op("leaky", []), _op("z", ["current_user"]), _op("a", ["check_permission"])]
|
|
|
|
|
cpg = _cpg(rows)
|
|
|
|
|
assert verdict(cpg, _spec(), function="leaky") == verdict(cpg, _spec(), function="leaky")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_the_spec_drives_the_query_parameters() -> None:
|
|
|
|
|
from openultrasast.cpg.backend import CpgResult
|
|
|
|
|
from openultrasast.model.dominance import verdict
|
|
|
|
|
|
|
|
|
|
seen: dict[str, object] = {}
|
|
|
|
|
|
|
|
|
|
def run(query: str, params: dict[str, object]) -> object:
|
|
|
|
|
seen["query"] = query
|
|
|
|
|
seen.update(params)
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
verdict(CpgResult(cpg_path=Path("c.bin"), run=run), _spec(), function="f")
|
|
|
|
|
assert seen["query"] == "dominance"
|
|
|
|
|
assert seen["operations"] == ("filter_by", "query.get", "session.delete")
|
|
|
|
|
assert seen["dischargers"] == ("login_required", "current_user", "check_permission")
|
|
|
|
|
assert seen["function"] == "f"
|