Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a06e89-93a2-7aa2-82b3-ce5ccd2682e6
389 lines
14 KiB
Python
389 lines
14 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import Sequence
|
|
|
|
from kings_guard.cadence import EmissionCadence
|
|
from kings_guard.contracts import (
|
|
STREAM_FINDING_PREFIX,
|
|
EffectorRequest,
|
|
ImmuneObservation,
|
|
ImmuneSignal,
|
|
PostureAssessment,
|
|
PostureEvaluation,
|
|
PostureLevel,
|
|
ReconciliationView,
|
|
RestrictiveDirection,
|
|
SecurityGenome,
|
|
SecurityPhenotype,
|
|
SignalKind,
|
|
StreamAssessment,
|
|
StreamCompleteness,
|
|
StreamHeartbeat,
|
|
)
|
|
from kings_guard.stream import evaluate_stream as assess_stream
|
|
|
|
CRITICAL_FINDINGS = frozenset(
|
|
{
|
|
"tenant_mismatch",
|
|
"undeclared_capability",
|
|
"unexpected_egress_destination",
|
|
"credential_exfil_probe",
|
|
}
|
|
)
|
|
ELEVATED_FINDINGS = frozenset(
|
|
{
|
|
"constraint_probe_or_boundary_trip",
|
|
"unexpected_protocol",
|
|
"unverified_identity_binding",
|
|
"control_plane_error",
|
|
}
|
|
)
|
|
RISK_WEIGHTS = {
|
|
"tenant_mismatch": 50,
|
|
"undeclared_capability": 40,
|
|
"unexpected_egress_destination": 50,
|
|
"credential_exfil_probe": 60,
|
|
"constraint_probe_or_boundary_trip": 25,
|
|
"unexpected_protocol": 15,
|
|
"unverified_identity_binding": 20,
|
|
"control_plane_error": 20,
|
|
}
|
|
|
|
UNKNOWN_STREAM_REASON = (
|
|
"per-observation evaluation cannot vouch for stream completeness; "
|
|
"the record in hand was scored for richness only"
|
|
)
|
|
|
|
|
|
class PostureEvaluator:
|
|
def evaluate(
|
|
self,
|
|
genome: SecurityGenome,
|
|
observation: ImmuneObservation,
|
|
*,
|
|
stream: StreamAssessment | None = None,
|
|
) -> PostureEvaluation:
|
|
phenotype = self._derive_phenotype(genome, observation)
|
|
assessment = self._assess(phenotype, observation, stream=stream)
|
|
signals = self._build_signals(observation, assessment, stream=stream)
|
|
return PostureEvaluation(
|
|
phenotype=phenotype,
|
|
assessment=assessment,
|
|
signals=signals,
|
|
stream=stream,
|
|
)
|
|
|
|
def evaluate_stream(
|
|
self,
|
|
observations: Sequence[ImmuneObservation],
|
|
cadence: EmissionCadence,
|
|
*,
|
|
now: str,
|
|
watching_since: str | None = None,
|
|
heartbeats: Sequence[StreamHeartbeat] = (),
|
|
reconciliation: ReconciliationView | None = None,
|
|
) -> StreamAssessment:
|
|
return assess_stream(
|
|
observations,
|
|
cadence,
|
|
now=now,
|
|
watching_since=watching_since,
|
|
heartbeats=heartbeats,
|
|
reconciliation=reconciliation,
|
|
)
|
|
|
|
def evaluate_with_stream(
|
|
self,
|
|
genome: SecurityGenome,
|
|
observation: ImmuneObservation,
|
|
cadence: EmissionCadence,
|
|
*,
|
|
now: str,
|
|
observations: Sequence[ImmuneObservation] | None = None,
|
|
watching_since: str | None = None,
|
|
heartbeats: Sequence[StreamHeartbeat] = (),
|
|
reconciliation: ReconciliationView | None = None,
|
|
) -> PostureEvaluation:
|
|
stream = self.evaluate_stream(
|
|
observations if observations is not None else (observation,),
|
|
cadence,
|
|
now=now,
|
|
watching_since=watching_since,
|
|
heartbeats=heartbeats,
|
|
reconciliation=reconciliation,
|
|
)
|
|
return self.evaluate(genome, observation, stream=stream)
|
|
|
|
def _derive_phenotype(
|
|
self,
|
|
genome: SecurityGenome,
|
|
observation: ImmuneObservation,
|
|
) -> SecurityPhenotype:
|
|
findings: list[str] = []
|
|
tolerated: list[str] = []
|
|
|
|
if observation.decision is observation.decision.HEARTBEAT:
|
|
return SecurityPhenotype(
|
|
subject_id=observation.subject_id,
|
|
tenant_id=observation.tenant_id,
|
|
observed_capability=observation.capability,
|
|
protocol=observation.protocol,
|
|
decision=observation.decision,
|
|
active_findings=(),
|
|
tolerated_findings=(),
|
|
)
|
|
|
|
if observation.tenant_id != genome.tenant_id:
|
|
findings.append("tenant_mismatch")
|
|
if observation.capability not in genome.permitted_capabilities:
|
|
findings.append("undeclared_capability")
|
|
if observation.protocol not in genome.permitted_protocols:
|
|
findings.append("unexpected_protocol")
|
|
if (
|
|
observation.egress_destination is not None
|
|
and observation.egress_destination not in genome.permitted_egress
|
|
):
|
|
findings.append("unexpected_egress_destination")
|
|
|
|
if observation.identity_binding and observation.identity_binding != "verified_token":
|
|
tolerance = _matching_tolerance(
|
|
genome, "identity_binding", observation.identity_binding
|
|
)
|
|
if tolerance is None:
|
|
findings.append("unverified_identity_binding")
|
|
else:
|
|
tolerated.append(f"tolerated:{tolerance.tolerance_id}")
|
|
|
|
if observation.decision.value == "deny" and observation.deny_reason == "credential_exfil":
|
|
findings.append("credential_exfil_probe")
|
|
elif observation.decision.value == "deny" and observation.deny_reason == "arg_constraint":
|
|
findings.append("constraint_probe_or_boundary_trip")
|
|
elif observation.decision.value == "error":
|
|
findings.append("control_plane_error")
|
|
|
|
return SecurityPhenotype(
|
|
subject_id=observation.subject_id,
|
|
tenant_id=observation.tenant_id,
|
|
observed_capability=observation.capability,
|
|
protocol=observation.protocol,
|
|
decision=observation.decision,
|
|
active_findings=tuple(findings),
|
|
tolerated_findings=tuple(tolerated),
|
|
)
|
|
|
|
def _assess(
|
|
self,
|
|
phenotype: SecurityPhenotype,
|
|
observation: ImmuneObservation,
|
|
*,
|
|
stream: StreamAssessment | None,
|
|
) -> PostureAssessment:
|
|
findings = set(phenotype.active_findings)
|
|
|
|
posture = PostureLevel.HEALTHY
|
|
if findings & CRITICAL_FINDINGS:
|
|
posture = PostureLevel.INFLAMED
|
|
elif findings:
|
|
posture = PostureLevel.ELEVATED
|
|
if observation.decision.value == "allow" and "unexpected_egress_destination" in findings:
|
|
posture = PostureLevel.COMPROMISED
|
|
|
|
risk_score = 5 + sum(RISK_WEIGHTS.get(item, 10) for item in findings)
|
|
if posture is PostureLevel.ELEVATED:
|
|
risk_score = max(risk_score, 40)
|
|
elif posture is PostureLevel.INFLAMED:
|
|
risk_score = max(risk_score, 80)
|
|
elif posture is PostureLevel.COMPROMISED:
|
|
risk_score = max(risk_score, 95)
|
|
risk_score = min(risk_score, 100)
|
|
|
|
# Richness of the record received — never of the stream.
|
|
confidence_score = 70
|
|
if observation.policy_version is not None:
|
|
confidence_score += 10
|
|
if observation.latency_ms is not None:
|
|
confidence_score += 5
|
|
if observation.resource_scope:
|
|
confidence_score += 5
|
|
confidence_score = min(confidence_score, 95)
|
|
|
|
if stream is None:
|
|
completeness = StreamCompleteness.UNKNOWN
|
|
completeness_reason = UNKNOWN_STREAM_REASON
|
|
else:
|
|
completeness = stream.completeness
|
|
completeness_reason = stream.reason
|
|
|
|
rationale = _build_rationale(
|
|
posture=posture,
|
|
findings=phenotype.active_findings,
|
|
tolerated_findings=phenotype.tolerated_findings,
|
|
completeness=completeness,
|
|
completeness_reason=completeness_reason,
|
|
)
|
|
return PostureAssessment(
|
|
posture=posture,
|
|
risk_score=risk_score,
|
|
confidence_score=confidence_score,
|
|
stream_completeness=completeness,
|
|
completeness_reason=completeness_reason,
|
|
findings=phenotype.active_findings,
|
|
tolerated_findings=phenotype.tolerated_findings,
|
|
rationale=rationale,
|
|
)
|
|
|
|
def _build_signals(
|
|
self,
|
|
observation: ImmuneObservation,
|
|
assessment: PostureAssessment,
|
|
*,
|
|
stream: StreamAssessment | None,
|
|
) -> tuple[ImmuneSignal, ...]:
|
|
signals: list[ImmuneSignal] = []
|
|
if assessment.posture is not PostureLevel.HEALTHY:
|
|
if observation.source_system == "qonto-assistant":
|
|
signals.append(self._build_qonto_pilot_signal(observation, assessment))
|
|
else:
|
|
signals.append(
|
|
ImmuneSignal(
|
|
signal_id=f"sig:{observation.observation_id}",
|
|
signal_kind=SignalKind.OBSERVATION_ALERT,
|
|
posture=assessment.posture,
|
|
summary=assessment.rationale,
|
|
target_system=observation.source_system,
|
|
findings=assessment.findings,
|
|
metadata={"source_system": observation.source_system},
|
|
)
|
|
)
|
|
if stream is not None and stream.findings:
|
|
signals.append(self._build_stream_signal(observation, assessment, stream))
|
|
return tuple(signals)
|
|
|
|
def _build_stream_signal(
|
|
self,
|
|
observation: ImmuneObservation,
|
|
assessment: PostureAssessment,
|
|
stream: StreamAssessment,
|
|
) -> ImmuneSignal:
|
|
return ImmuneSignal(
|
|
signal_id=f"sig:stream:{observation.observation_id}",
|
|
signal_kind=SignalKind.STREAM_COMPLETENESS,
|
|
posture=assessment.posture,
|
|
summary=stream.reason,
|
|
target_system=observation.source_system,
|
|
findings=stream.findings,
|
|
metadata={
|
|
"stream_completeness": stream.completeness.value,
|
|
"finding_class": "stream",
|
|
},
|
|
)
|
|
|
|
def _build_qonto_pilot_signal(
|
|
self,
|
|
observation: ImmuneObservation,
|
|
assessment: PostureAssessment,
|
|
) -> ImmuneSignal:
|
|
if "credential_exfil_probe" in assessment.findings:
|
|
action = "lock_actor_temporarily"
|
|
direction: RestrictiveDirection = "reduce_authority"
|
|
reason = (
|
|
"Observed a credential-exfil deny signal; qonto-assistant should activate "
|
|
"its fast local loop lockout and preserve metadata-only evidence."
|
|
)
|
|
else:
|
|
action = "tighten_actor_scrutiny"
|
|
direction = "require_step_up"
|
|
reason = (
|
|
"Observed repeated policy-boundary pressure; qonto-assistant should tighten "
|
|
"local scrutiny without delegating final authorization to kings-guard."
|
|
)
|
|
|
|
signal_id = f"sig:{observation.observation_id}"
|
|
return ImmuneSignal(
|
|
signal_id=signal_id,
|
|
signal_kind=SignalKind.POSTURE_HINT,
|
|
posture=assessment.posture,
|
|
summary=reason,
|
|
target_system="qonto-assistant",
|
|
findings=assessment.findings,
|
|
effector_requests=(
|
|
EffectorRequest(
|
|
target_system="qonto-assistant",
|
|
action=action,
|
|
authority_boundary="advisory_only",
|
|
reason=reason,
|
|
requires_human_approval=False,
|
|
originating_observation_id=observation.observation_id,
|
|
originating_signal_id=signal_id,
|
|
stream_completeness=assessment.stream_completeness,
|
|
restrictive_direction=direction,
|
|
),
|
|
EffectorRequest(
|
|
target_system="state-hub",
|
|
action="record_non_secret_incident_evidence",
|
|
authority_boundary="metadata_only",
|
|
reason="Preserve posture evidence without copying secret values.",
|
|
requires_human_approval=False,
|
|
originating_observation_id=observation.observation_id,
|
|
originating_signal_id=signal_id,
|
|
stream_completeness=assessment.stream_completeness,
|
|
restrictive_direction="none",
|
|
),
|
|
),
|
|
metadata={
|
|
"pilot_lane": "qonto-assistant",
|
|
"resource_scope": observation.resource_scope or "unknown",
|
|
"originating_observation_id": observation.observation_id,
|
|
},
|
|
)
|
|
|
|
|
|
def _matching_tolerance(
|
|
genome: SecurityGenome,
|
|
field_name: str,
|
|
field_value: str,
|
|
):
|
|
for tolerance in genome.tolerances:
|
|
if tolerance.match_field == field_name and tolerance.match_value == field_value:
|
|
return tolerance
|
|
return None
|
|
|
|
|
|
def _build_rationale(
|
|
*,
|
|
posture: PostureLevel,
|
|
findings: tuple[str, ...],
|
|
tolerated_findings: tuple[str, ...],
|
|
completeness: StreamCompleteness,
|
|
completeness_reason: str,
|
|
) -> str:
|
|
if posture is PostureLevel.HEALTHY:
|
|
if tolerated_findings:
|
|
content = "Healthy posture with tolerated deviations only: " + ", ".join(
|
|
tolerated_findings
|
|
)
|
|
else:
|
|
content = "Healthy posture: observation is compatible with declared intent."
|
|
else:
|
|
detail = ", ".join(findings) if findings else "no active findings"
|
|
tolerated = (
|
|
f" Tolerated deviations still present: {', '.join(tolerated_findings)}."
|
|
if tolerated_findings
|
|
else ""
|
|
)
|
|
content = f"{posture.value.title()} posture driven by {detail}.{tolerated}"
|
|
|
|
if completeness is StreamCompleteness.COMPLETE:
|
|
stream_text = "Stream completeness is complete."
|
|
elif completeness is StreamCompleteness.DEGRADED:
|
|
stream_text = f"This judgment rests on a stream I cannot vouch for ({completeness_reason})."
|
|
else:
|
|
stream_text = (
|
|
"Stream completeness is unknown; confidence scores the record, "
|
|
f"not the stream ({completeness_reason})."
|
|
)
|
|
return f"{content} {stream_text}"
|
|
|
|
|
|
def is_stream_finding(finding: str) -> bool:
|
|
return finding.startswith(STREAM_FINDING_PREFIX)
|