bastion / environment.py
Astro-Dude's picture
Upload environment.py with huggingface_hub
0b363a4 verified
Raw
History Blame Contribute Delete
5.77 kB
"""
Bastion: Cybersecurity Incident Response — Core Environment
Extends openenv Environment for full spec compliance.
"""
from __future__ import annotations
import random
from typing import Any, Optional
from uuid import uuid4
from openenv.core.env_server import Environment
from models import (
IncidentAction,
IncidentObservation,
IncidentState,
ActionType,
ACTION_NAMES,
NUM_ACTIONS,
make_observation,
)
from dynamics import step_dynamics, generate_forensic_report
from reward import (
compute_step_reward,
compute_penalties,
compute_task_weighted_score,
compute_baseline_comparison,
)
from baseline import run_baseline
from tasks import get_task, TaskConfig
class BastionEnvironment(Environment[IncidentAction, IncidentObservation, IncidentState]):
"""
OpenEnv-compatible RL environment for cybersecurity incident response.
The agent is an Incident Commander responding to a live cyberattack.
12 time steps (hours), 10 possible actions, 8 network systems.
"""
def __init__(self) -> None:
super().__init__()
self._task: Optional[TaskConfig] = None
self._state: IncidentState = IncidentState()
self._rng: random.Random = random.Random(42)
self._action_history: list[tuple[int, int]] = []
self._cumulative_reward: float = 0.0
self._baseline_state: Optional[IncidentState] = None
self._done: bool = False
self._initialized: bool = False
self._alerts_accurate: bool = False
def reset(
self,
seed: Optional[int] = None,
episode_id: Optional[str] = None,
**kwargs: Any,
) -> IncidentObservation:
task_id = kwargs.get("task_id", "easy_1")
self._task = get_task(task_id)
self._state = self._task.initial_state.clone()
self._state.episode_id = episode_id or str(uuid4())
self._state.step_count = 0
self._state.task_id = task_id
effective_seed = seed if seed is not None else self._task.seed
self._rng = random.Random(effective_seed)
self._action_history = []
self._cumulative_reward = 0.0
self._done = False
self._initialized = True
self._alerts_accurate = False
# Pre-compute baseline
self._baseline_state = run_baseline(task_id, policy_name="naive")
return make_observation(
self._state,
self._rng,
task_description=self._task.description,
done=False,
reward=None,
alerts_accurate=False,
)
def step(
self,
action: IncidentAction,
timeout_s: Optional[float] = None,
**kwargs: Any,
) -> IncidentObservation:
if not self._initialized:
self.reset(task_id="easy_1")
if self._done:
self.reset(task_id=self._state.task_id)
action_idx = action.action
target_idx = action.target_system
# Snapshot for reward
prev_state = self._state.clone()
# Execute transition
stamina_cost, alerts_accurate, team_msgs = step_dynamics(
self._state, action_idx, target_idx, self._rng
)
self._alerts_accurate = alerts_accurate
self._action_history.append((action_idx, target_idx))
# Compute reward
step_reward = compute_step_reward(prev_state, self._state, stamina_cost)
penalty = compute_penalties(self._state)
total_reward = step_reward + penalty
self._cumulative_reward += total_reward
# Check termination
done = False
info: dict[str, Any] = {
"hour": self._state.hour,
"action_name": ACTION_NAMES.get(action_idx, str(action_idx)),
"target_system": self._state.systems[target_idx].name if target_idx < len(self._state.systems) else str(target_idx),
"stamina_cost": round(stamina_cost, 3),
"step_reward": round(step_reward, 6),
"penalty": round(penalty, 6),
}
if self._state.data_exfiltrated >= 1.0:
done = True
info["termination_reason"] = "total_data_breach"
elif self._state.hour >= self._task.max_hours:
done = True
info["termination_reason"] = "time_expired"
if done:
self._done = True
agent_score = compute_task_weighted_score(
self._state, self._task.scoring_weights
)
baseline_score = compute_task_weighted_score(
self._baseline_state, self._task.scoring_weights
)
comparison = compute_baseline_comparison(
self._state, self._baseline_state, self._task.scoring_weights
)
info["agent_final_score"] = round(agent_score, 6)
info["baseline_final_score"] = round(baseline_score, 6)
info["comparison_score"] = round(comparison, 6)
info["cumulative_reward"] = round(self._cumulative_reward, 6)
info["data_exfiltrated"] = round(self._state.data_exfiltrated, 4)
info["attacker_progress"] = round(self._state.attacker_progress, 4)
info["final_state"] = self._state.snapshot()
info["forensic_report"] = generate_forensic_report(self._state)
obs = make_observation(
self._state,
self._rng,
task_description=self._task.description if not done else "",
done=done,
reward=total_reward,
alerts_accurate=alerts_accurate,
team_messages=team_msgs,
)
obs.metadata = info
return obs
@property
def state(self) -> IncidentState:
return self._state