Custom scenariosΒΆ
Subclass BaseScenario: calculate_reward(agent_id, global_state, effect) and
check_termination(global_state). Exploits stay in the functional core.
from netforge_rl.scenarios.base_scenario import BaseScenario
class CustomRansomware(BaseScenario):
def calculate_reward(self, agent_id, global_state, effect=None):
reward = 0.0
if effect and getattr(effect, 'cost', 0) > 0:
reward -= effect.cost * 0.05
if 'red' in agent_id:
return self._red_reward(global_state, effect)
return self._blue_reward(global_state, effect)
def _red_reward(self, global_state, effect):
if not (effect and effect.success and effect.state_deltas):
return 0.0
r = 0.0
for key, val in effect.state_deltas.items():
if 'privilege' in key and val == 'Root':
r += 5.0
if 'system_integrity' in key and val == 'kinetic_destruction':
r += 10000.0
return r
def _blue_reward(self, global_state, effect):
hosts = global_state.all_hosts.values()
healthy = sum(
1 for h in hosts if h.compromised_by == 'None' and h.status != 'isolated'
)
return healthy / len(global_state.all_hosts)
def check_termination(self, global_state):
dead = any(
h.system_integrity == 'kinetic_destruction'
for h in global_state.all_hosts.values()
)
return {agent: dead for agent in self.agents}
ot_stuxnet: PLC ODE physics, OverloadPLC moves setpoints, Blue has ~10 ticks
after SCADA_PHYSICAL_ALARM to isolate. Tables: OT/SCADA physics.
env = NetForgeRLEnv({'scenario_type': 'ot_stuxnet', 'max_ticks': 300})
env = NetForgeRLEnv({'scenario_type': 'custom_ransomware'})