mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-11-13 01:48:17 +00:00
feat(detection-core): adapt authorization traces
This commit is contained in:
@@ -17,6 +17,7 @@ from openultrasast.semantic.obligations.abstract_domain import ( # noqa: E402
|
||||
ResourceRef,
|
||||
WitnessMatch,
|
||||
)
|
||||
from openultrasast.semantic.obligations.core_adapter import to_legacy_trace_record # noqa: E402
|
||||
from openultrasast.semantic.obligations.trace import AbstractTraceState, Guard, Hop, Trace # noqa: E402
|
||||
|
||||
|
||||
@@ -55,14 +56,14 @@ def _trace() -> Trace:
|
||||
|
||||
|
||||
def test_trace_natively_conforms_to_core_schema() -> None:
|
||||
record = _trace().to_core_record(entry="src/app.py::handler", digests={"input": "abc123"})
|
||||
record = to_legacy_trace_record(_trace(), entry="src/app.py::handler", digests={"input": "abc123"})
|
||||
assert isinstance(record, TraceRecord)
|
||||
assert record.entry == "src/app.py::handler"
|
||||
assert record.judgment is Judgment.UNGUARDED
|
||||
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()
|
||||
assert record.canonical() == to_legacy_trace_record(_trace(), entry="src/app.py::handler", digests={"input": "abc123"}).canonical()
|
||||
|
||||
|
||||
def test_legacy_trace_without_abstract_state_is_indeterminate() -> None:
|
||||
@@ -82,7 +83,7 @@ def test_legacy_trace_without_abstract_state_is_indeterminate() -> None:
|
||||
dominating=trace.dominating,
|
||||
missing=trace.missing,
|
||||
)
|
||||
record = legacy.to_core_record(entry="e", digests={})
|
||||
record = to_legacy_trace_record(legacy, entry="e", digests={})
|
||||
assert record.judgment is Judgment.INDETERMINATE
|
||||
assert record.completeness_reasons
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ from openultrasast.semantic.obligations.abstract_domain import ( # noqa: E402
|
||||
ResourceRef,
|
||||
WitnessMatch,
|
||||
)
|
||||
from openultrasast.semantic.obligations.core_adapter import to_legacy_trace_record # noqa: E402
|
||||
from openultrasast.semantic.obligations.trace import AbstractTraceState, Trace # noqa: E402
|
||||
|
||||
REPO = Path(__file__).resolve().parents[1]
|
||||
@@ -67,7 +68,7 @@ def test_end_to_end_push_through_core_contracts() -> None:
|
||||
missing="identity_constraint",
|
||||
abstract_state=state,
|
||||
)
|
||||
record = trace.to_core_record(entry="src/app.py::handler", digests={"input": "abc123"})
|
||||
record = to_legacy_trace_record(trace, 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"
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
"""Authorization traces cross into detection-core v2 only through the adapter."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
from dataclasses import replace
|
||||
|
||||
from test_core_evidence import _scope, _stamp
|
||||
from test_core_native import _trace
|
||||
from test_core_reference_families import _contract, _load
|
||||
|
||||
from openultrasast.core import evidence, family, semantics
|
||||
from openultrasast.semantic.obligations import trace as trace_module
|
||||
from openultrasast.semantic.obligations.abstract_domain import PairJudgment, ResourceRef
|
||||
from openultrasast.semantic.obligations.core_adapter import to_evidence_record
|
||||
|
||||
|
||||
def _adapt(trace: trace_module.Trace) -> evidence.EvidenceRecord:
|
||||
contract = _contract(_load("obligation_family.json"))
|
||||
stamp = replace(_stamp(), family_contract_digest=family.family_contract_digest(contract))
|
||||
return to_evidence_record(
|
||||
trace,
|
||||
contract=contract,
|
||||
ecosystem="reference-obligation",
|
||||
scope=_scope(),
|
||||
provenance=stamp,
|
||||
)
|
||||
|
||||
|
||||
def test_direct_complete_authorization_trace_maps_to_v2_violation() -> None:
|
||||
trace = replace(_trace(), hops=(), operation_line=3)
|
||||
|
||||
record = _adapt(trace)
|
||||
|
||||
assert record.decision.outcome is evidence.NeutralOutcome.VIOLATION
|
||||
assert record.decision.family_code == "constraint_exposed"
|
||||
assert tuple(item.role for item in record.packet.transitions) == ("operation", "constraint")
|
||||
assert all(item.status is semantics.RelationStatus.COMPLETE for item in record.packet.transitions)
|
||||
|
||||
|
||||
def test_missing_per_hop_completeness_stays_incomplete_and_indeterminate() -> None:
|
||||
trace = replace(_trace(), operation_line=3)
|
||||
|
||||
record = _adapt(trace)
|
||||
|
||||
constraint = next(item for item in record.packet.transitions if item.role == "constraint")
|
||||
assert constraint.status is semantics.RelationStatus.INCOMPLETE
|
||||
assert record.decision.outcome is evidence.NeutralOutcome.INDETERMINATE
|
||||
assert record.decision.family_code == "constraint_unknown"
|
||||
assert "missing_constraint_relation" in record.packet.completeness_reasons
|
||||
|
||||
|
||||
def test_complete_discharged_trace_maps_to_v2_satisfaction() -> None:
|
||||
trace = replace(_trace(), hops=(), operation_line=3)
|
||||
assert trace.abstract_state is not None
|
||||
state = replace(
|
||||
trace.abstract_state,
|
||||
path_judgment=replace(trace.abstract_state.path_judgment, judgment=PairJudgment.DISCHARGED, missing=()),
|
||||
)
|
||||
|
||||
record = _adapt(replace(trace, abstract_state=state))
|
||||
|
||||
assert record.decision.outcome is evidence.NeutralOutcome.SATISFIED
|
||||
assert record.decision.family_code == "constraint_enforced"
|
||||
|
||||
|
||||
def test_unknown_resource_degrades_complete_legacy_judgment_to_indeterminate() -> None:
|
||||
trace = replace(_trace(), hops=(), operation_line=3)
|
||||
assert trace.abstract_state is not None
|
||||
state = replace(trace.abstract_state, resource=ResourceRef(kind="unknown"))
|
||||
|
||||
record = _adapt(replace(trace, abstract_state=state))
|
||||
|
||||
assert record.decision.outcome is evidence.NeutralOutcome.INDETERMINATE
|
||||
assert record.packet.transitions[1].status is semantics.RelationStatus.INCOMPLETE
|
||||
|
||||
|
||||
def test_unset_abstract_state_never_lifts_legacy_trace_to_a_clean_result() -> None:
|
||||
record = _adapt(replace(_trace(), hops=(), operation_line=3, abstract_state=None))
|
||||
|
||||
assert record.decision.outcome is evidence.NeutralOutcome.INDETERMINATE
|
||||
assert record.packet.transitions[1].status is semantics.RelationStatus.INCOMPLETE
|
||||
|
||||
|
||||
def test_authorization_trace_domain_has_no_core_import_or_conversion_method() -> None:
|
||||
source = inspect.getsource(trace_module)
|
||||
|
||||
assert "openultrasast.core" not in source
|
||||
assert not hasattr(trace_module.Trace, "to_core_record")
|
||||
Reference in New Issue
Block a user