sentinel-env / inference.py
rudrapatel-1908's picture
Update inference.py
484482f verified
Raw
History Blame Contribute Delete
10.7 kB
#!/usr/bin/env python3
"""
inference.py β€” Sentinel Env Multi-Agent Inference Script
OpenEnv 2026 Architecture β€” Session-isolated, WebSocket-ready
Follows [START]/[STEP]/[END] stdout format.
Runs ONE task per invocation via TASK_NAME env var.
"""
import os
import json
import requests
from openai import OpenAI
# ── Credentials ──
API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
MODEL_NAME = os.getenv("MODEL_NAME", "meta-llama/Meta-Llama-3-8B-Instruct")
HF_TOKEN = os.getenv("HF_TOKEN")
if HF_TOKEN is None:
raise ValueError("HF_TOKEN environment variable is required")
SPACE_URL = os.getenv("SPACE_URL", "https://rudrapatel-1908-sentinel-env.hf.space")
TASK_NAME = os.getenv("TASK_NAME", "easy-lockdown")
BENCHMARK = "sentinel_env"
MAX_STEPS = 8
client = OpenAI(api_key=HF_TOKEN, base_url=API_BASE_URL)
# ── HTTP Helpers ──
def env_reset(task_id: str) -> dict:
r = requests.post(f"{SPACE_URL}/reset", json={"task_id": task_id}, timeout=30)
r.raise_for_status()
return r.json()
def env_step(command: str, target_id: str) -> dict:
r = requests.post(f"{SPACE_URL}/step",
json={"command": command, "target_id": target_id}, timeout=30)
r.raise_for_status()
return r.json()
def warroom_reset() -> dict:
r = requests.post(f"{SPACE_URL}/warroom/reset",
json={"task_id": "red-vs-blue"}, timeout=30)
r.raise_for_status()
return r.json()
def warroom_step(att_type, att_res, scn_type, scn_tip, scn_res, rem_type, rem_tip, rem_res) -> dict:
payload = {
"attacker": {"action_type": att_type, "target_resource": att_res},
"scanner": {"action_type": scn_type, "target_ip": scn_tip, "target_resource": scn_res},
"remediator": {"action_type": rem_type, "target_ip": rem_tip, "target_resource": rem_res},
}
r = requests.post(f"{SPACE_URL}/warroom/step", json=payload, timeout=30)
r.raise_for_status()
return r.json()
# ── LLM: Single-agent action ──
def get_single_action(task_id: str, obs: dict) -> dict:
terminal = obs.get("terminal_output", "")
inventory = json.dumps(obs.get("inventory", []), indent=2)
rules = {
"easy-lockdown": 'command="lockdown", target_id="s3-vault"',
"easy-secrets": 'Step 1: command="audit", target_id="api-key-01" β†’ Step 2: command="revoke", target_id="api-key-01" β†’ Step 3: command="rotate", target_id="api-key-01"',
"medium-access": 'command="revoke_admin", target_id="user-dev-01"',
"medium-mfa": 'Step 1: command="audit", target_id="iam-users" β†’ Then: command="enforce_mfa", target_id="admin-alice" (repeat for admin-bob and admin-carol)',
"hard-breach": 'Step 1: command="block_ip", target_id="attacker-ip" β†’ Step 2: command="close_port", target_id="web-server"',
"critical-ransomware": 'Step 1: command="isolate", target_id="db-server" β†’ Step 2: command="revoke_sessions", target_id="active-sessions" β†’ Step 3: command="restore_backup", target_id="db-server"',
"expert-apt": 'Step 1: command="detect_c2", target_id="c2-beacon" β†’ Step 2: command="block_outbound", target_id="outbound-fw" β†’ Step 3: command="isolate_host", target_id="infected-host" β†’ Step 4: command="patch_vulnerability", target_id="vuln-cve-2024"',
}
prompt = f"""You are a cybersecurity AI agent for Sentinel-Env.
Task: {task_id}
Terminal output: {terminal}
Current inventory: {inventory}
Correct action sequence for this task:
{rules.get(task_id, 'Follow the terminal instructions.')}
Based on the terminal output, choose the NEXT correct action.
Respond ONLY with a JSON object. No markdown. No explanation.
Example: {{"command": "lockdown", "target_id": "s3-vault"}}"""
resp = client.chat.completions.create(
model=MODEL_NAME,
messages=[{"role": "user", "content": prompt}],
max_tokens=64,
temperature=0,
)
raw = resp.choices[0].message.content.strip()
raw = raw.replace("```json", "").replace("```", "").strip()
return json.loads(raw)
# ── LLM: Multi-agent actions ──
def _ask_llm(prompt: str) -> dict:
try:
resp = client.chat.completions.create(
model=MODEL_NAME,
messages=[{"role": "user", "content": prompt}],
max_tokens=80, temperature=0,
)
raw = resp.choices[0].message.content.strip().replace("```json","").replace("```","").strip()
return json.loads(raw)
except Exception:
return {}
def get_multi_actions(obs: dict) -> dict:
scn_obs = obs.get("scanner_obs", {})
rem_obs = obs.get("remediator_obs", {})
att_obs = obs.get("attacker_obs", {})
att = _ask_llm(f"""You are the Red Team attacker.
Visible resources: {att_obs.get('visible_resources',[])}
Position: {att_obs.get('current_position','unknown')}
Data stolen: {att_obs.get('data_exfiltrated',False)}
action_type options: port_scan, privilege_escalation, lateral_movement, data_exfiltration, hide_tracks
target_resource options: s3-vault, db-server, secret-vault, web-server, user-admin
Respond ONLY with JSON: {{"action_type":"...","target_resource":"..."}}""")
scn = _ask_llm(f"""You are the Blue Team Scanner.
IP connections: {scn_obs.get('ip_connections',[])}
Flagged threats: {scn_obs.get('flagged_threats',[])}
Logs: {scn_obs.get('security_logs',[])[-2:]}
action_type options: scan_logs, flag_threat, share_intel
Respond ONLY with JSON: {{"action_type":"...","target_ip":"45.33.32.156","target_resource":"web-server"}}""")
rem = _ask_llm(f"""You are the Blue Team Remediator.
Open ports: {rem_obs.get('open_ports',[])}
Locked resources: {rem_obs.get('locked_resources',[])}
Intel from Scanner: {rem_obs.get('received_intel',[])}
action_type options: block_ip, close_port, lockdown, revoke_access
Respond ONLY with JSON: {{"action_type":"...","target_ip":"45.33.32.156","target_resource":"web-server"}}""")
# Safe defaults
if not att.get("action_type"): att = {"action_type": "port_scan", "target_resource": "web-server"}
if not scn.get("action_type"): scn = {"action_type": "scan_logs", "target_resource": "web-server"}
if not rem.get("action_type"): rem = {"action_type": "close_port", "target_resource": "web-server"}
return {"attacker": att, "scanner": scn, "remediator": rem}
# ── Single-agent runner ──
def run_single_task(task_id: str) -> None:
result = env_reset(task_id)
obs = result["observation"]
rewards = []
done = False
success = False
print(f"[START] task={task_id} env={BENCHMARK} model={MODEL_NAME}", flush=True)
for step_num in range(1, MAX_STEPS + 1):
action_str = "null"
reward = 0.05
error = "null"
try:
action = get_single_action(task_id, obs)
cmd = action["command"]
tgt = action["target_id"]
action_str = f"{cmd}({tgt})"
result = env_step(cmd, tgt)
obs = result["observation"]
reward = float(result["reward"])
done = result["done"]
except Exception as e:
error = str(e).replace("\n", " ")
rewards.append(reward)
print(f"[STEP] step={step_num} action={action_str} "
f"reward={reward:.2f} done={str(done).lower()} error={error}", flush=True)
if done:
success = True
break
rewards_str = ",".join(f"{r:.2f}" for r in rewards)
print(f"[END] success={str(success).lower()} steps={len(rewards)} "
f"rewards={rewards_str}", flush=True)
# ── Multi-agent runner ──
def run_warroom_task() -> None:
result = warroom_reset()
obs = result
rewards = []
done = False
success = False
print(f"[START] task=red-vs-blue env={BENCHMARK} model={MODEL_NAME}", flush=True)
for step_num in range(1, MAX_STEPS + 1):
action_str = "null"
reward = 0.05
error = "null"
try:
acts = get_multi_actions(obs)
att = acts["attacker"]
scn = acts["scanner"]
rem = acts["remediator"]
action_str = (
f"red:{att.get('action_type','?')}({att.get('target_resource','?')})|"
f"scan:{scn.get('action_type','?')}|"
f"rem:{rem.get('action_type','?')}"
)
result = warroom_step(
att.get("action_type","port_scan"), att.get("target_resource","web-server"),
scn.get("action_type","scan_logs"), scn.get("target_ip",""), scn.get("target_resource","web-server"),
rem.get("action_type","close_port"), rem.get("target_ip",""), rem.get("target_resource","web-server"),
)
obs = result.get("observation", obs)
rwds = result.get("rewards", {})
done = result.get("done", False)
blue = (rwds.get("scanner", 0.05) + rwds.get("remediator", 0.05)) / 2
reward = round(max(0.01, min(0.99, blue)), 2)
except Exception as e:
error = str(e).replace("\n", " ")
rewards.append(reward)
print(f"[STEP] step={step_num} action={action_str} "
f"reward={reward:.2f} done={str(done).lower()} error={error}", flush=True)
if done:
att_obs = obs.get("attacker_obs", {}) if isinstance(obs, dict) else {}
success = not att_obs.get("data_exfiltrated", True)
break
rewards_str = ",".join(f"{r:.2f}" for r in rewards)
print(f"[END] success={str(success).lower()} steps={len(rewards)} "
f"rewards={rewards_str}", flush=True)
# ── Main ──
# ── Main β€” runs ONLY the task in TASK_NAME ──
def main():
try:
requests.get(f"{SPACE_URL}/health", timeout=15).raise_for_status()
except Exception:
print("[END] success=false steps=0 rewards=0.05", flush=True)
raise SystemExit(1)
valid_tasks = [
"easy-lockdown",
"easy-secrets",
"medium-access",
"medium-mfa",
"hard-breach",
"critical-ransomware",
"expert-apt",
"red-vs-blue",
]
if TASK_NAME not in valid_tasks:
print("[END] success=false steps=0 rewards=0.05", flush=True)
raise SystemExit(1)
try:
if TASK_NAME == "red-vs-blue":
run_warroom_task()
else:
run_single_task(TASK_NAME)
except Exception:
print("[END] success=false steps=0 rewards=0.05", flush=True)
raise SystemExit(1)
if __name__ == "__main__":
main()