"""Deterministic scenario registry for SecOps Evidence Gym.""" from __future__ import annotations from copy import deepcopy from random import Random from typing import Any DEFAULT_TASK_ID = "secret_exposure_easy" TOOL_CATALOG: list[dict[str, Any]] = [ { "name": "list_assets", "description": "List synthetic services, routes, and artifact collections.", "args": {}, }, { "name": "get_log_events", "description": "Return sanitized telemetry evidence ids for a service/query.", "args": {"service_id": "str", "query": "str"}, }, { "name": "check_security_headers", "description": "Inspect a service header snapshot and return pass/fail evidence.", "args": {"service_id": "str"}, }, { "name": "search_repo", "description": "Search synthetic repo/config snippets for evidence ids.", "args": {"query": "str"}, }, { "name": "scan_dependencies", "description": "Inspect a synthetic dependency manifest excerpt.", "args": {}, }, { "name": "create_finding", "description": "Store a candidate finding for verifier review.", "args": { "finding_type": "str", "evidence_ids": "list[str]", "severity_guess": "str", "remediation": "str", }, }, { "name": "validate_finding", "description": "Run the deterministic verifier for a candidate finding.", "args": {"finding_id": "str"}, }, { "name": "submit_report", "description": "Submit the final structured report and end the episode.", "args": {"report_json": "dict"}, }, ] SCENARIOS: dict[str, dict[str, Any]] = { "secret_exposure_easy": { "task_id": "secret_exposure_easy", "difficulty": "easy", "title": "Secret exposure in repo snapshot", "alert": ( "Repository hygiene alert: a synthetic config snapshot may contain " "credential-like material. Investigate, cite evidence, validate, and " "submit a remediation report." ), "ground_truth_id": "GT-SECRET-001", "finding_type": "secret_exposure", "severity": "high", "required_evidence": ["EVID-101"], "remediation_keywords": ["rotate", "remove"], "impact_keywords": ["secret", "credential", "api key", "leak"], "assets": { "services": ["gateway", "profile-service", "admin-service"], "routes": ["/login", "/profile", "/admin/export"], "collections": ["repo_snapshot", "telemetry", "headers", "dependencies"], }, "repo": [ { "evidence_id": "EVID-101", "source": "repo_snapshot/config/profile-service.env", "service_id": "profile-service", "tags": ["api key", "secret", "credential", "config"], "text": ( "PROFILE_EXPORT_API_KEY=CYBERLAB_DO_NOT_USE_API_KEY_7F3A " "# synthetic lab credential" ), }, { "evidence_id": "EVID-102", "source": "repo_snapshot/config/gateway.env", "service_id": "gateway", "tags": ["config", "benign"], "text": "LOG_LEVEL=info; FEATURE_SAFE_HEADERS=true", }, ], "logs": [ { "evidence_id": "EVID-103", "service_id": "profile-service", "tags": ["benign", "startup"], "text": "profile-service boot completed for synthetic tenant secops-lab", } ], "headers": { "gateway": { "evidence_id": "EVID-104", "present": ["Strict-Transport-Security", "Content-Security-Policy"], "missing": [], } }, "dependencies": [ { "evidence_id": "EVID-105", "source": "repo_snapshot/requirements.lock", "tags": ["dependency", "benign"], "text": "fastapi==0.115.0; pydantic==2.8.2", } ], }, "missing_security_headers_medium": { "task_id": "missing_security_headers_medium", "difficulty": "medium", "title": "Missing security headers", "alert": ( "Gateway response-hardening alert: verify whether required security " "headers are missing or weak and submit evidence-backed remediation." ), "ground_truth_id": "GT-HEADERS-001", "finding_type": "missing_security_headers", "severity": "medium", "required_evidence": ["EVID-201"], "remediation_keywords": ["hsts", "csp"], "impact_keywords": ["header", "hsts", "csp", "clickjacking"], "assets": { "services": ["gateway", "profile-service", "admin-service"], "routes": ["/login", "/profile", "/admin/export"], "collections": ["repo_snapshot", "telemetry", "headers", "dependencies"], }, "repo": [ { "evidence_id": "EVID-202", "source": "repo_snapshot/gateway/security_headers.py", "service_id": "gateway", "tags": ["headers", "config"], "text": "X-Frame-Options is set, but HSTS and CSP are not configured.", } ], "logs": [ { "evidence_id": "EVID-203", "service_id": "gateway", "tags": ["benign", "response"], "text": "GET /profile 200 request_id=req-442 synthetic header audit", } ], "headers": { "gateway": { "evidence_id": "EVID-201", "present": ["X-Frame-Options", "X-Content-Type-Options"], "missing": ["Strict-Transport-Security", "Content-Security-Policy"], } }, "dependencies": [ { "evidence_id": "EVID-204", "source": "repo_snapshot/requirements.lock", "tags": ["dependency", "benign"], "text": "starlette==0.38.2; uvicorn==0.30.1", } ], }, "authz_boundary_hard": { "task_id": "authz_boundary_hard", "difficulty": "hard", "title": "Authorisation boundary misconfiguration", "alert": ( "Access-control drift alert: investigate a route/role mismatch in the " "synthetic admin service and submit a validated remediation report." ), "ground_truth_id": "GT-AUTHZ-001", "finding_type": "authz_boundary_misconfiguration", "severity": "critical", "required_evidence": ["EVID-301"], "supporting_evidence": ["EVID-302"], "remediation_keywords": ["least privilege", "policy", "regression"], "impact_keywords": ["authorization", "authorisation", "role", "admin"], "assets": { "services": ["gateway", "profile-service", "admin-service"], "routes": ["/login", "/profile", "/admin/export"], "collections": ["repo_snapshot", "telemetry", "headers", "dependencies"], }, "repo": [ { "evidence_id": "EVID-301", "source": "repo_snapshot/admin-service/policy_matrix.yaml", "service_id": "admin-service", "tags": ["authorization", "role", "policy", "admin export"], "text": ( "route=/admin/export allowed_roles=[admin, analyst] " "expected_roles=[admin]" ), } ], "logs": [ { "evidence_id": "EVID-302", "service_id": "admin-service", "tags": ["authorization", "role", "admin export"], "text": ( "request_id=req-913 route=/admin/export role=analyst " "decision=allow synthetic boundary-check event" ), }, { "evidence_id": "EVID-303", "service_id": "gateway", "tags": ["benign", "auth"], "text": "request_id=req-912 route=/profile role=user decision=allow", }, ], "headers": { "admin-service": { "evidence_id": "EVID-304", "present": ["Strict-Transport-Security", "Content-Security-Policy"], "missing": [], } }, "dependencies": [ { "evidence_id": "EVID-305", "source": "repo_snapshot/requirements.lock", "tags": ["dependency", "benign"], "text": "pyyaml==6.0.2; fastapi==0.115.0", } ], }, } def list_task_ids() -> list[str]: return list(SCENARIOS) def build_scenario(task_id: str | None, seed: int | None = None) -> dict[str, Any]: """Return a deep-copied scenario with deterministic benign variation.""" selected_task_id = task_id if task_id in SCENARIOS else DEFAULT_TASK_ID scenario = deepcopy(SCENARIOS[selected_task_id]) scenario["seed"] = seed rng = Random(seed if seed is not None else 0) service_alias_sets = [ ["gateway", "profile-service", "admin-service"], ["edge-gateway", "user-profile", "admin-service"], ["public-gateway", "profile-api", "backoffice-admin"], ] aliases = service_alias_sets[rng.randrange(len(service_alias_sets))] original_services = scenario["assets"]["services"] alias_map = dict(zip(original_services, aliases, strict=True)) scenario["service_aliases"] = alias_map scenario["assets"]["services"] = [alias_map.get(s, s) for s in original_services] for collection_name in ("repo", "logs"): for item in scenario.get(collection_name, []): service_id = item.get("service_id") if service_id in alias_map: item["service_id"] = alias_map[service_id] scenario["headers"] = { alias_map.get(service_id, service_id): snapshot for service_id, snapshot in scenario.get("headers", {}).items() } for entries_name in ("repo", "logs", "dependencies"): rng.shuffle(scenario.get(entries_name, [])) return scenario