mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-11-13 01:48:17 +00:00
feat(detection-core): 8.1 prover native conformance to core trace/coverage contracts
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""Conformance tests: the prover's trace/coverage satisfy the detection-core contracts (task 3.1, 3.2)."""
|
||||
"""Native conformance tests: prover Trace/Coverage implement the core contracts directly (task 8.1)."""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -7,7 +7,6 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from openultrasast.core.trace import TraceRecord # noqa: E402
|
||||
from openultrasast.core.verdict import Judgment # noqa: E402
|
||||
from openultrasast.hunt.core_adapter import coverage_to_core, trace_to_core # noqa: E402
|
||||
from openultrasast.hunt.prover import Coverage, PairJudgmentCounts # noqa: E402
|
||||
from openultrasast.semantic.obligations.abstract_domain import ( # noqa: E402
|
||||
AuthorityRef,
|
||||
@@ -27,14 +26,7 @@ def _trace() -> Trace:
|
||||
judgment=PairJudgment.UNGUARDED,
|
||||
missing=("identity_constraint",),
|
||||
completeness=Completeness(is_complete=True, reasons=()),
|
||||
matches=(
|
||||
WitnessMatch(
|
||||
relation=EvidenceRelation.MUST,
|
||||
resource_relation=EvidenceRelation.MUST,
|
||||
authority_relation=EvidenceRelation.NO,
|
||||
evidence=("fact:checkPermission",),
|
||||
),
|
||||
),
|
||||
matches=(WitnessMatch(EvidenceRelation.MUST, EvidenceRelation.MUST, EvidenceRelation.NO, ("fact:f",)),),
|
||||
),
|
||||
resource=ResourceRef(kind="exact", values=("credentials",)),
|
||||
authority=AuthorityRef(values=("admin",), is_unknown=False),
|
||||
@@ -47,18 +39,14 @@ def _trace() -> Trace:
|
||||
access="public",
|
||||
parameters=("request",),
|
||||
hops=(
|
||||
Hop(
|
||||
function="handler",
|
||||
call_line=10,
|
||||
guards_before_call=(Guard("permission_check", "path", 9, "fact:f", "f1", "all_paths", "branches"),),
|
||||
forwarded=(),
|
||||
),
|
||||
Hop("handler", 10, (Guard("permission_check", "path", 9, "fact:f", "f1", "all_paths", "branches"),), ()),
|
||||
Hop("helper", 20, (Guard("inner", "path", 19, "fact:g", "g1", "some_paths", "returns"),), ()),
|
||||
),
|
||||
operation_kind="protected_read",
|
||||
fact_id="obligation:protected_read:src/app.py:30",
|
||||
operation_line=30,
|
||||
arguments=(),
|
||||
guards_after=(),
|
||||
guards_after=(Guard("after", "op", 31, "fact:h", "h1", "none", "exits"),),
|
||||
requires=("identity_constraint",),
|
||||
dominating=(),
|
||||
missing="identity_constraint",
|
||||
@@ -66,26 +54,18 @@ def _trace() -> Trace:
|
||||
)
|
||||
|
||||
|
||||
def test_trace_conforms_to_core_schema() -> None:
|
||||
record = trace_to_core(_trace(), entry="src/app.py::handler", digests={"input": "abc123"})
|
||||
def test_trace_natively_conforms_to_core_schema() -> None:
|
||||
record = _trace().to_core_record(entry="src/app.py::handler", digests={"input": "abc123"})
|
||||
assert isinstance(record, TraceRecord)
|
||||
assert record.entry == "src/app.py::handler"
|
||||
assert record.operation_kind == "protected_read"
|
||||
assert record.resource == "credentials"
|
||||
assert record.authority == "admin"
|
||||
assert record.judgment is Judgment.UNGUARDED
|
||||
assert record.hops[0].function == "handler"
|
||||
assert record.hops[0].complete is False # conservative default: Trace carries no completeness
|
||||
assert trace_to_core(_trace(), entry="e", digests={}, complete=True).hops[0].complete is True
|
||||
assert record.guards[0].kind == "permission_check"
|
||||
assert record.guards[0].effect == "branches"
|
||||
assert record.witnesses[0].provenance == "fact:checkPermission"
|
||||
assert record.digests == {"input": "abc123"}
|
||||
# deterministic serialization round-trips
|
||||
assert record.canonical() == trace_to_core(_trace(), entry="src/app.py::handler", digests={"input": "abc123"}).canonical()
|
||||
assert record.resource == "credentials"
|
||||
assert [g.kind for g in record.guards] == ["permission_check", "inner", "after"]
|
||||
assert record.hops[0].complete is False # conservative default
|
||||
assert record.canonical() == _trace().to_core_record(entry="src/app.py::handler", digests={"input": "abc123"}).canonical()
|
||||
|
||||
|
||||
def test_trace_without_abstract_state_is_indeterminate() -> None:
|
||||
def test_legacy_trace_without_abstract_state_is_indeterminate() -> None:
|
||||
trace = _trace()
|
||||
legacy = Trace(
|
||||
entry_kind=trace.entry_kind,
|
||||
@@ -102,12 +82,12 @@ def test_trace_without_abstract_state_is_indeterminate() -> None:
|
||||
dominating=trace.dominating,
|
||||
missing=trace.missing,
|
||||
)
|
||||
record = trace_to_core(legacy, entry="src/app.py::handler", digests={})
|
||||
record = legacy.to_core_record(entry="e", digests={})
|
||||
assert record.judgment is Judgment.INDETERMINATE
|
||||
assert record.completeness_reasons # legacy trace without abstract state is incomplete
|
||||
assert record.completeness_reasons
|
||||
|
||||
|
||||
def test_coverage_conforms_to_core_schema() -> None:
|
||||
def test_coverage_natively_conforms_to_core_schema() -> None:
|
||||
coverage = Coverage(
|
||||
files_by_language={"java": 2},
|
||||
frameworks_recognised={"jenkins": 1},
|
||||
@@ -120,17 +100,10 @@ def test_coverage_conforms_to_core_schema() -> None:
|
||||
pair_judgments=PairJudgmentCounts(unguarded=1, discharged=1, indeterminate=2),
|
||||
resolution_counts={"complete_calls": 5, "incomplete_calls": 3, "unresolved_calls": 2},
|
||||
)
|
||||
core = coverage_to_core(coverage)
|
||||
assert core.entry_points == 3
|
||||
assert core.frameworks_recognised == {"jenkins": 1}
|
||||
assert core.calls == 10
|
||||
assert core.complete == 5 and core.incomplete == 3 and core.unresolved == 2
|
||||
assert core.unchecked_reasons == {"unreached": 1}
|
||||
core = coverage.to_core()
|
||||
assert core.calls == 10 and core.complete == 5 and core.incomplete == 3 and core.unresolved == 2
|
||||
core.validate()
|
||||
|
||||
|
||||
def test_coverage_adapter_refuses_unknown_resolution_keys() -> None:
|
||||
coverage = Coverage(
|
||||
bad = Coverage(
|
||||
files_by_language={},
|
||||
frameworks_recognised={},
|
||||
entry_points=1,
|
||||
@@ -143,36 +116,7 @@ def test_coverage_adapter_refuses_unknown_resolution_keys() -> None:
|
||||
resolution_counts={"complete_calls": 2, "mystery_key": 7},
|
||||
)
|
||||
try:
|
||||
coverage_to_core(coverage)
|
||||
raise AssertionError("unknown resolution keys must fail loud, not under-count calls")
|
||||
bad.to_core()
|
||||
raise AssertionError("unknown resolution keys must fail loud")
|
||||
except ValueError as error:
|
||||
assert "mystery_key" in str(error)
|
||||
|
||||
|
||||
def test_adapter_maps_guards_from_every_hop() -> None:
|
||||
trace = _trace()
|
||||
second = Hop(
|
||||
function="helper",
|
||||
call_line=20,
|
||||
guards_before_call=(Guard("inner", "path", 19, "fact:g", "g1", "some_paths", "returns"),),
|
||||
forwarded=(),
|
||||
)
|
||||
with_second = Trace(
|
||||
entry_kind=trace.entry_kind,
|
||||
boundary=trace.boundary,
|
||||
access=trace.access,
|
||||
parameters=trace.parameters,
|
||||
hops=(*trace.hops, second),
|
||||
operation_kind=trace.operation_kind,
|
||||
fact_id=trace.fact_id,
|
||||
operation_line=trace.operation_line,
|
||||
arguments=trace.arguments,
|
||||
guards_after=(Guard("after", "op", 31, "fact:h", "h1", "none", "exits"),),
|
||||
requires=trace.requires,
|
||||
dominating=trace.dominating,
|
||||
missing=trace.missing,
|
||||
abstract_state=trace.abstract_state,
|
||||
)
|
||||
record = trace_to_core(with_second, entry="e", digests={})
|
||||
kinds = [guard.kind for guard in record.guards]
|
||||
assert kinds == ["permission_check", "inner", "after"]
|
||||
@@ -9,7 +9,6 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
from openultrasast.core.admission import AdmissionManifestError, validate_report_schema # noqa: E402
|
||||
from openultrasast.core.consumer import PushRequest, consume_verdict # noqa: E402
|
||||
from openultrasast.core.verdict import Judgment, ProvenClaim # noqa: E402
|
||||
from openultrasast.hunt.core_adapter import trace_to_core # noqa: E402
|
||||
from openultrasast.semantic.obligations.abstract_domain import ( # noqa: E402
|
||||
AuthorityRef,
|
||||
Completeness,
|
||||
@@ -68,7 +67,7 @@ def test_end_to_end_push_through_core_contracts() -> None:
|
||||
missing="identity_constraint",
|
||||
abstract_state=state,
|
||||
)
|
||||
record = trace_to_core(trace, entry="src/app.py::handler", digests={"input": "abc123"})
|
||||
record = trace.to_core_record(entry="src/app.py::handler", digests={"input": "abc123"})
|
||||
claim = ProvenClaim(trace_id="t1", witness="fact:f", path="src/app.py", line=30, family="access_control")
|
||||
verdict = consume_verdict(PushRequest(base="b", head="h", budget_class="dev-pool"), record, (claim,), ())
|
||||
assert verdict.claims[0].witness == "fact:f"
|
||||
|
||||
Reference in New Issue
Block a user