soc-openenv / soc_env /models.py
Battlecon's picture
Flatten diretory structure
03b6159
Raw
History Blame Contribute Delete
6.53 kB
"""
Typed Pydantic v2 models for the SOC Incident Response OpenEnv environment.
All models are fully typed and JSON-serialisable — required by the OpenEnv spec.
"""
from __future__ import annotations
from typing import Any, Dict, List, Literal, Optional
from enum import Enum
from pydantic import BaseModel, Field
# ---------------------------------------------------------------------------
# Enumerations
# ---------------------------------------------------------------------------
class AlertSeverity(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
class AlertStatus(str, Enum):
PENDING = "pending"
INVESTIGATING = "investigating"
TRUE_POSITIVE = "true_positive"
FALSE_POSITIVE = "false_positive"
CONTAINED = "contained"
ESCALATED = "escalated"
CLOSED = "closed"
class ActionType(str, Enum):
ENRICH_ALERT = "enrich_alert"
CORRELATE_ALERTS = "correlate_alerts"
ISOLATE_ENDPOINT = "isolate_endpoint"
DISABLE_ACCOUNT = "disable_account"
COLLECT_FORENSICS = "collect_forensics"
ESCALATE_TO_TIER2 = "escalate_to_tier2"
CREATE_TICKET = "create_ticket"
class MITRETactic(str, Enum):
INITIAL_ACCESS = "initial_access"
EXECUTION = "execution"
PERSISTENCE = "persistence"
PRIVILEGE_ESCALATION = "privilege_escalation"
DEFENSE_EVASION = "defense_evasion"
CREDENTIAL_ACCESS = "credential_access"
DISCOVERY = "discovery"
LATERAL_MOVEMENT = "lateral_movement"
COLLECTION = "collection"
EXFILTRATION = "exfiltration"
IMPACT = "impact"
class HostStatus(str, Enum):
CLEAN = "clean"
SUSPICIOUS = "suspicious"
COMPROMISED = "compromised"
ISOLATED = "isolated"
PATCHED = "patched"
# ---------------------------------------------------------------------------
# Domain entities
# ---------------------------------------------------------------------------
class ThreatIndicator(BaseModel):
type: Literal["ip", "hash", "domain", "user", "process", "registry_key"]
value: str
reputation: Optional[str] = None
context: Optional[str] = None
class SIEMAlert(BaseModel):
alert_id: str
timestamp: str
severity: AlertSeverity
rule_name: str
description: str
host_id: str
user_id: Optional[str] = None
mitre_tactic: Optional[MITRETactic] = None
mitre_technique: Optional[str] = None
indicators: List[ThreatIndicator] = Field(default_factory=list)
raw_log: Optional[str] = None
status: AlertStatus = AlertStatus.PENDING
enrichment: Optional[Dict[str, Any]] = None
ground_truth: Optional[bool] = None # True=TP, False=FP — never sent to agent
class NetworkHost(BaseModel):
host_id: str
hostname: str
ip_address: str
subnet: str
os: str
role: str
owner: Optional[str] = None
is_critical: bool = False
is_vip: bool = False
status: HostStatus = HostStatus.CLEAN
active_sessions: List[str] = Field(default_factory=list)
running_processes: List[str] = Field(default_factory=list)
class AttackChain(BaseModel):
"""Ground-truth kill chain — used only by grader, never exposed to agent."""
patient_zero_host: str
stages: List[MITRETactic]
lateral_movement_targets: List[str] = Field(default_factory=list)
crown_jewel_host: str
exfiltration_complete: bool = False
attacker_dwell_minutes: int = 0
class BusinessConstraint(BaseModel):
host_id: Optional[str] = None
user_id: Optional[str] = None
constraint_type: Literal[
"cannot_isolate", "legal_hold", "customer_facing", "executive", "critical_infra"
]
reason: str
severity: Literal["advisory", "hard_block"]
class InvestigationNote(BaseModel):
step: int
action_taken: str
finding: str
timestamp: str
# ---------------------------------------------------------------------------
# OpenEnv spec: Observation, Action, Reward
# ---------------------------------------------------------------------------
class Observation(BaseModel):
"""What the agent sees at each step — ground_truth fields are excluded."""
step: int
task_id: str
task_description: str
active_alerts: List[SIEMAlert]
acknowledged_alerts: List[SIEMAlert] = Field(default_factory=list)
hosts: List[NetworkHost]
business_constraints: List[BusinessConstraint] = Field(default_factory=list)
notes: List[InvestigationNote] = Field(default_factory=list)
elapsed_minutes: int = 0
max_minutes: int = 120
steps_remaining: int = 40
last_action_result: Optional[str] = None
last_action_success: bool = True
def model_dump_safe(self) -> Dict[str, Any]:
"""Serialise without ground_truth — safe to send to agent."""
d = self.model_dump()
for alert in d.get("active_alerts", []):
alert.pop("ground_truth", None)
for alert in d.get("acknowledged_alerts", []):
alert.pop("ground_truth", None)
return d
class Action(BaseModel):
"""The agent's action at each step."""
action_type: ActionType
alert_id: Optional[str] = None
alert_ids: Optional[List[str]] = None
host_id: Optional[str] = None
user_id: Optional[str] = None
artifact_types: Optional[List[str]] = None
source: Optional[str] = None
priority: Optional[Literal["P1", "P2", "P3"]] = None
summary: Optional[str] = None
classification: Optional[Literal["true_positive", "false_positive"]] = None
correlation_hypothesis: Optional[str] = None
class Reward(BaseModel):
"""Per-step reward with full breakdown."""
total: float = Field(ge=-1.0, le=1.0)
classification_delta: float = 0.0
chain_coverage_delta: float = 0.0
containment_delta: float = 0.0
dwell_penalty: float = 0.0
fp_penalty: float = 0.0
compliance_delta: float = 0.0
info: str = ""
class EnvState(BaseModel):
"""Full internal state — includes ground truth. Returned by state()."""
task_id: str
step: int
done: bool
cumulative_reward: float
observation: Observation
attack_chain: Optional[AttackChain] = None
agent_classifications: Dict[str, bool] = Field(default_factory=dict)
identified_stages: List[MITRETactic] = Field(default_factory=list)
isolated_hosts: List[str] = Field(default_factory=list)
disabled_accounts: List[str] = Field(default_factory=list)
forensics_collected: Dict[str, List[str]] = Field(default_factory=dict)
escalated: bool = False
ticket_created: bool = False