mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 19:59:26 +00:00
feat(authorization-obligations): export obligation shapes through the existing store
Task 1.3 (Req 2.1, 2.2, 2.4). - seed.pair_lessons derives obligation shapes for absence-mechanism rows and falls back to the sink shape when no obligation is learned, so the flow store is unchanged; skip reasons name the accepted dischargers. - append_from_pair accepts both shape families (guard carries the discharger kind); search_tree ignores obligation rows; obligation_mechanisms selects them. - Measured on vibe-py: the two vampi pairs seed protected_read / identity_constraint / authenticated_context. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
ec3a1438e8
commit
8324b243a0
@@ -14,6 +14,7 @@ from pathlib import Path
|
||||
from ..complexity.map import Hotspot
|
||||
from ..findings import StaticFinding
|
||||
from ..index import CodeChunk, EmbeddingClient, VectorIndex, VectorRecord, write_vector_index
|
||||
from .obligations.shapes import ObligationShape
|
||||
from .overlay import OverlayRecord
|
||||
from .variants import Shape
|
||||
|
||||
@@ -96,14 +97,14 @@ def append_mechanism(
|
||||
_TIER_RANK = {"seeded": 1, "reviewed": 2}
|
||||
|
||||
|
||||
def corpus_mechanism_id(shape: Shape) -> str:
|
||||
def corpus_mechanism_id(shape: Shape | ObligationShape) -> str:
|
||||
"""Deterministic id from the shape key, so the same shape gets the same record on every machine (lever-addressable)."""
|
||||
return "corpus:" + hashlib.sha1(shape.key().encode()).hexdigest()[:16]
|
||||
|
||||
|
||||
def append_from_pair(
|
||||
store: MechanismStore,
|
||||
shape: Shape,
|
||||
shape: Shape | ObligationShape,
|
||||
*,
|
||||
summary: str,
|
||||
cwe: str,
|
||||
@@ -120,7 +121,15 @@ def append_from_pair(
|
||||
if existing is not None and _TIER_RANK.get(existing.review_tier, 0) > _TIER_RANK.get(tier, 0):
|
||||
tier = existing.review_tier # a shape keeps the strongest review it ever had
|
||||
inherited = tuple(tag for tag in (existing.tags if existing is not None else ()) if tag.startswith(("provenance:", "tier:")))
|
||||
positions = ", ".join(f"arg{p} ({k})" for p, k in zip(shape.source_positions, shape.source_kinds, strict=True))
|
||||
if isinstance(shape, Shape):
|
||||
positions = ", ".join(f"arg{p} ({k})" for p, k in zip(shape.source_positions, shape.source_kinds, strict=True))
|
||||
exploitable = f"{positions} reaches {shape.sink_name}/{shape.arity}; the fix added {shape.guard}"
|
||||
keywords: tuple[str, ...] = (shape.sink_name, shape.guard, *shape.source_kinds)
|
||||
guard = shape.guard
|
||||
else: # obligation shape: the discharger the fix carries is the 'guard' the reports show
|
||||
exploitable = f"{shape.operation_kind} reached without {shape.discharger_kind}; the fix binds {shape.provenance}"
|
||||
keywords = (shape.operation_kind, shape.discharger_kind, shape.provenance, shape.resource_class)
|
||||
guard = shape.discharger_kind
|
||||
record = Mechanism(
|
||||
id=mechanism_id,
|
||||
summary=summary,
|
||||
@@ -129,18 +138,18 @@ def append_from_pair(
|
||||
tags=(
|
||||
shape.mechanism,
|
||||
f"mechanism:{shape.mechanism}",
|
||||
f"guard:{shape.guard}",
|
||||
f"guard:{guard}",
|
||||
*sorted({*inherited, f"provenance:{provenance}", f"tier:{tier}"}),
|
||||
),
|
||||
keywords=(shape.sink_name, shape.guard, *shape.source_kinds),
|
||||
what_made_it_exploitable=f"{positions} reaches {shape.sink_name}/{shape.arity}; the fix added {shape.guard}",
|
||||
keywords=keywords,
|
||||
what_made_it_exploitable=exploitable,
|
||||
source_finding_id="",
|
||||
source_repo="",
|
||||
origin="corpus",
|
||||
review_tier=tier,
|
||||
pairs=pairs,
|
||||
shape=shape.to_dict(),
|
||||
guard=shape.guard,
|
||||
guard=guard,
|
||||
)
|
||||
store.append(record)
|
||||
return record
|
||||
|
||||
@@ -16,6 +16,8 @@ from ..preprocess import LANGUAGE_BY_EXTENSION
|
||||
from .facts import FactLoadError, SemanticFacts, load_facts
|
||||
from .ir import FileIR, parse_file
|
||||
from .mechanisms import MechanismStore, append_from_pair
|
||||
from .obligations.facts import ObligationFacts, load_obligation_facts
|
||||
from .obligations.shapes import ABSENCE_MECHANISMS, ObligationShape, derive_obligation, explain_skip
|
||||
from .variants import Shape, derive_shape
|
||||
|
||||
LANGUAGE_UNSUPPORTED = "variants_language_unsupported"
|
||||
@@ -85,7 +87,7 @@ def export_mechanisms(cases: Sequence[PairCase], store: MechanismStore, *, facts
|
||||
if lessons:
|
||||
seeded.append(case.name)
|
||||
else:
|
||||
skipped.append((case.name, "no_labeled_sink_call_site"))
|
||||
skipped.append((case.name, _skip_detail(case, vuln_ir, fixed_ir, vuln_text, fixed_text, loaded)))
|
||||
return ExportReport(
|
||||
seeded=len(seeded),
|
||||
records=len(record_ids),
|
||||
@@ -97,9 +99,9 @@ def export_mechanisms(cases: Sequence[PairCase], store: MechanismStore, *, facts
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Lesson:
|
||||
"""One shape a trusted pair teaches, with the CWE and a text-free summary."""
|
||||
"""One shape a trusted pair teaches, with the CWE and a text-free summary (a sink shape or an obligation shape)."""
|
||||
|
||||
shape: Shape
|
||||
shape: Shape | ObligationShape
|
||||
cwe: str
|
||||
summary: str
|
||||
|
||||
@@ -125,6 +127,27 @@ def pair_lessons(
|
||||
for row in case.expected:
|
||||
if not row.function or not row.mechanism:
|
||||
continue
|
||||
obligation_kind = getattr(row, "obligation", None)
|
||||
if obligation_kind or row.mechanism in ABSENCE_MECHANISMS:
|
||||
obligation = derive_obligation(
|
||||
vuln_ir,
|
||||
fixed_ir,
|
||||
function=row.function,
|
||||
obligation=obligation_kind,
|
||||
mechanism=row.mechanism,
|
||||
facts=_obligation_facts(),
|
||||
flow_facts=facts,
|
||||
vuln_text=vuln_text,
|
||||
fixed_text=fixed_text,
|
||||
)
|
||||
if obligation is not None:
|
||||
summary = (
|
||||
f"{obligation.mechanism}: {obligation.operation_kind} without {obligation.discharger_kind} ({row.cwe}); "
|
||||
f"fix binds {obligation.provenance}"
|
||||
)
|
||||
lessons.append(Lesson(shape=obligation, cwe=row.cwe, summary=summary))
|
||||
continue
|
||||
# an absence row that teaches no obligation may still teach a sink shape (a weak literal at a sink, say)
|
||||
shape = derive_shape(
|
||||
vuln_ir,
|
||||
fixed_ir,
|
||||
@@ -143,6 +166,32 @@ def pair_lessons(
|
||||
return lessons
|
||||
|
||||
|
||||
def _skip_detail(case: PairCase, vuln_ir: FileIR, fixed_ir: FileIR, vuln_text: str, fixed_text: str, flow_facts: SemanticFacts) -> str:
|
||||
"""Why a parsing, labeled pair taught nothing: the obligation derivation's reason for absence rows, else the sink reason."""
|
||||
for row in case.expected:
|
||||
if row.function and row.mechanism and (getattr(row, "obligation", None) or row.mechanism in ABSENCE_MECHANISMS):
|
||||
return explain_skip(
|
||||
vuln_ir,
|
||||
fixed_ir,
|
||||
function=row.function,
|
||||
vuln_text=vuln_text,
|
||||
fixed_text=fixed_text,
|
||||
obligation=getattr(row, "obligation", None),
|
||||
facts=_obligation_facts(),
|
||||
flow_facts=flow_facts,
|
||||
)
|
||||
return "no_labeled_sink_call_site"
|
||||
|
||||
|
||||
_OBLIGATION_FACTS: list[ObligationFacts] = []
|
||||
|
||||
|
||||
def _obligation_facts() -> ObligationFacts:
|
||||
if not _OBLIGATION_FACTS:
|
||||
_OBLIGATION_FACTS.append(load_obligation_facts())
|
||||
return _OBLIGATION_FACTS[0]
|
||||
|
||||
|
||||
def _skip_reason(case: PairCase) -> str | None:
|
||||
if case.review_tier not in GATING_TIERS:
|
||||
return f"tier:{case.review_tier}"
|
||||
|
||||
@@ -38,7 +38,8 @@ def search_tree(
|
||||
max_mechanisms: int,
|
||||
) -> SearchResult:
|
||||
"""Variant search over the parsed files of a tree with the store's corpus shapes (bounded by ``max_mechanisms``)."""
|
||||
records = corpus_mechanisms(store.load())[: max(max_mechanisms, 0)]
|
||||
records = [record for record in corpus_mechanisms(store.load()) if (record.shape or {}).get("family", "sink") == "sink"]
|
||||
records = records[: max(max_mechanisms, 0)]
|
||||
shapes: list[Shape] = []
|
||||
ids: dict[str, str] = {}
|
||||
degradations: list[dict[str, object]] = []
|
||||
|
||||
Reference in New Issue
Block a user