Spaces:
Sleeping
Sleeping
File size: 7,459 Bytes
7f42e9d 9d4e851 7f42e9d 9d4e851 7f42e9d 9d4e851 7f42e9d 9d4e851 587e1de 7f42e9d 9d4e851 7f42e9d 9d4e851 7f42e9d b178e29 7f42e9d 9d4e851 1959b81 9d4e851 b66f312 9d4e851 21c335e 7f42e9d 9d4e851 7f42e9d 9d4e851 587e1de 996fd06 7f42e9d 9d4e851 7f42e9d 9d4e851 7f42e9d b178e29 7f42e9d 9d4e851 7f42e9d b178e29 9d4e851 7f42e9d 9d4e851 b66f312 9d4e851 b66f312 9d4e851 7f42e9d 9d4e851 b178e29 9d4e851 b178e29 9d4e851 b178e29 7f42e9d 9d02a80 7f42e9d 9d4e851 7f42e9d 9d4e851 7f42e9d 9d4e851 7f42e9d 9d02a80 9d4e851 7f42e9d 9d4e851 7f42e9d d76c1b1 1959b81 7f42e9d 9d4e851 1959b81 7f42e9d 9d4e851 7f42e9d b178e29 7f42e9d 9d4e851 b178e29 7f42e9d 1959b81 7f42e9d 9d4e851 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | """
inference.py — OpenEnv-compliant inference script for xsecure.
Required env vars:
HF_TOKEN Hugging Face / API key
API_BASE_URL LLM endpoint
MODEL_NAME Model identifier
"""
from __future__ import annotations
import asyncio
import json
import os
import re
import sys
from typing import Dict, List, Optional, Any
from dotenv import load_dotenv
from openai import OpenAI
from client import IncidentResponseEnv, StepResult
from models import IncidentAction, IncidentObservation
# Load .env for local dev
#load_dotenv()
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
"""
API_KEY = os.getenv("API_KEY", "")
API_BASE_URL = os.getenv("API_BASE_URL")
MODEL_NAME = os.getenv("MODEL_NAME")
ENV_URL = os.getenv("ENV_URL", "http://localhost:7860")
BENCHMARK = "xsecure"
MAX_STEPS = 20
"""
API_BASE_URL = os.environ["API_BASE_URL"]
MODEL_NAME = os.environ.get("MODEL_NAME", "gpt-4o-mini")
API_KEY = os.environ.get("API_KEY", "")
ENV_URL = os.environ.get("ENV_URL", "http://localhost:7860")
BENCHMARK = "xsecure"
MAX_STEPS = 20
if not API_KEY:
print("ERROR: HF_TOKEN is not set.", file=sys.stderr)
sys.exit(1)
# Use AsyncOpenAI to prevent blocking the event loop
llm = OpenAI(base_url=API_BASE_URL, api_key=API_KEY)
# ---------------------------------------------------------------------------
# Mandatory stdout loggers (Fixed spacing to match spec)
# ---------------------------------------------------------------------------
def log_start(task: str, env: str, model: str) -> None:
print(f"[START] task={task} env={env} model={model}", flush=True)
def log_step(step: int, action: str, reward: float, done: bool, error: Optional[str]) -> None:
# Spec requires double space after [STEP] for some parsers
print(
f"[STEP] step={step} action={action} reward={reward:.2f} "
f"done={str(done).lower()} error={error or 'null'}",
flush=True,
)
def log_end(success: bool, steps: int, score: float, rewards: List[float]) -> None:
print(
f"[END] success={str(success).lower()} steps={steps} score={score:.3f} "
f"rewards={','.join(f'{r:.2f}' for r in rewards)}",
flush=True,
)
# ---------------------------------------------------------------------------
# System prompt
# ---------------------------------------------------------------------------
SYSTEM_PROMPT = """\
You are an expert cybersecurity incident responder AI agent.
Your goal is to investigate logs and alerts, identify the threat, and mitigate it.
## Available Actions (one per step):
- analyze_log(log_id)
- trace_user(user_id)
- block_ip(ip_address)
- disable_account(user_id)
- restart_service(service)
- ignore
## Response Format (STRICT JSON):
{"action_type": "analyze_log", "target": "L001"}
"""
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _format_observation(obs: IncidentObservation) -> str:
# Use dot notation as expected by the environment models
"""logs_txt = "\n".join(f" [{l.log_id}] {l.timestamp} — {l.message}" for l in obs.logs)
alerts_txt = "\n".join(f" [{a.severity.upper()}] {a.message}" for a in obs.alerts)
services_txt = "\n".join(f" {s.name}: {s.status}" for s in obs.services)
"""
logs_txt = "\n".join(f" [{l['log_id']}] {l['timestamp']} — {l['message']}" for l in obs.logs)
alerts_txt = "\n".join(f" [{a['severity'].upper()}] {a.message}" for a in obs.alerts)
services_txt = "\n".join(f" {s['name']}: {s['status']}" for s in obs.services)
return (
f"=== Incident Dashboard (Step {obs.step_count}) ===\n\n"
f"LOGS:\n{logs_txt}\n\n"
f"ALERTS:\n{alerts_txt}\n\n"
f"SERVICES:\n{services_txt}\n\n"
f"ACTIVE USERS: {', '.join(obs.active_users)}\n\n"
f"Last action result: {obs.last_action_result}"
)
def _parse_action(text: str) -> IncidentAction:
"""Extract JSON action with filtering for extra fields to avoid Pydantic errors."""
try:
# 1. Try direct or markdown-wrapped JSON
pattern = re.search(r"(\{.*?\})", text.strip().replace("\n", " "), re.DOTALL)
if pattern:
data = json.loads(pattern.group(1))
# Only pass fields known to IncidentAction
valid_keys = {"action_type", "target"}
filtered = {k: v for k, v in data.items() if k in valid_keys}
return IncidentAction(**filtered)
except Exception:
pass
return IncidentAction(action_type="ignore", target="")
def _get_action(conversation: List[Dict], obs: IncidentObservation) -> IncidentAction:
conversation.append({"role": "user", "content": _format_observation(obs)})
response = llm.chat.completions.create(
model=MODEL_NAME,
messages=[{"role": "system", "content": SYSTEM_PROMPT}] + conversation,
max_tokens=256,
temperature=0.0,
)
text = response.choices[0].message.content or ""
conversation.append({"role": "assistant", "content": text})
return _parse_action(text)
# ---------------------------------------------------------------------------
# Episode runner
# ---------------------------------------------------------------------------
TASK_NAMES = {1: "brute-force-easy", 2: "suspicious-login-medium", 3: "multi-stage-apt-hard"}
def run_episode(task_id: int) -> None:
task_name = TASK_NAMES.get(task_id, f"task-{task_id}")
rewards: List[float] = []
steps_taken = 0
success = False
score = 0.0
conversation: List[Dict] = []
log_start(task=task_name, env=BENCHMARK, model=MODEL_NAME)
try:
with IncidentResponseEnv(base_url=ENV_URL) as env:
obs = env.reset(task_id=task_id)
for step in range(1, MAX_STEPS + 1):
# Now awaited correctly
action = _get_action(conversation, obs)
result = env.step(action)
rewards.append(result.reward)
steps_taken = step
obs = result.observation
log_step(
step=step,
action=f"{action.action_type}({action.target!r})",
reward=result.reward,
done=result.done,
error=None,
)
if result.done:
info = result.info or {}
# Robust score parsing
raw_score = info.get("final_score", 0.0)
score = min(max(float(raw_score or 0.0), 0.0), 1.0)
success = bool(info.get("success", False))
break
except Exception as e:
print(f"ERROR: Episode failed: {e}", file=sys.stderr)
finally:
log_end(success=success, steps=steps_taken, score=score, rewards=rewards)
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
task_ids_str = os.getenv("TASK_IDS", "1,2,3")
task_ids = [int(t.strip()) for t in task_ids_str.split(",") if t.strip()]
for task_id in task_ids:
run_episode(task_id)
if __name__ == "__main__":
main()
|