Phases 4-7: nested inference prompt, delegation reward, Delegation Score grader, multi-agent README
Browse filesPhase 4 β inference.py teaches the LLM about delegation:
- SYSTEM_PROMPT extended with two new action shapes (spawn_subagent / return_to_parent)
- Explicit delegation guidance: persist vs spawn tradeoff, productive=+0.05 / unproductive=-0.05
- LLMJsonAction & SecurityAuditAction Pydantic Literal extended to accept the new types
- [STEP] log line renders spawn_subagent(scope:target) for visibility
- parse_llm_action_text round-trip verified for both new action types
Phase 5 β reward shaping (already wired in Phase 3's _handle_return_to_parent):
- +0.05 productive, -0.05 unproductive, sub-agent findings flow into _submitted_findings
- spawn_subagent action itself returns +0.01 (tiny acknowledgement so the model sees the spawn was accepted)
- All shaping happens server-side; inference.py is unchanged in the loop body
Phase 6 β grader.py gains Delegation Score (5%):
- New parameter: subagent_outcomes (defaults to empty for single-agent backward compat)
- delegation_score = productive_spawns / total_spawns, defaults to 1.0 when no spawns
(so single-agent runs aren't penalised β neutral value)
- Replaced the redundant 5% "Any True Positive" bonus (already covered by detection_rate)
- Weights still sum to 100%; FP/honeypot/coverage_multiplier behavior unchanged
- Returned grades dict gains: delegation_score, subagent_total, subagent_productive
- Env's _finish_episode now plumbs _subagent_outcomes through to the grader
Phase 7 β README + openenv.yaml updates:
- Lead-with-thesis rewritten: triple-theme fit (world modeling + long-horizon + multi-agent)
- Action Space table adds spawn_subagent / return_to_parent
- New "Multi-Agent Delegation" subsection explaining when to spawn vs persist
- Scoring table swaps "Any True Positive 5%" β "Delegation Score 5%"
- openenv.yaml version 1.0.0 β 2.0.0 (action space changed; API consumers should re-pin)
All 78 tests still pass. Single-agent path is unchanged β agents that never call
spawn_subagent get delegation_score=1.0 (neutral), exactly the same final score
as before Phase 6's weight rebalance (modulo the dropped "Any TP" 5%, which any
agent that finds β₯1 vuln was already getting via detection_rate proportionally).
- README.md +22 -2
- inference.py +20 -1
- openenv.yaml +1 -1
- server/grader.py +27 -5
- server/security_audit_env_environment.py +1 -0
|
@@ -14,7 +14,13 @@ short_description: "Can your AI reason from raw evidence or just parse labels?"
|
|
| 14 |
|
| 15 |
**Live Environment:** https://huggingface.co/spaces/Sayuj63/Vapt-env
|
| 16 |
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
| Difficulty | Agent Sees | Regex Parser | Gemini 2.5 Flash |
|
| 20 |
|---|---|---|---|
|
|
@@ -156,8 +162,22 @@ with SecurityAuditEnv(base_url="http://localhost:8000").sync() as env:
|
|
| 156 |
| `list_tools` | See all available security audit tools |
|
| 157 |
| `use_tool` | Run a security tool (requires tool_name + arguments) |
|
| 158 |
| `submit_finding` | Document a discovered vulnerability |
|
|
|
|
|
|
|
| 159 |
| `generate_report` | End the audit and get the final score |
|
| 160 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
### Available Tools
|
| 162 |
|
| 163 |
| Tool | Description | Parameters |
|
|
@@ -313,7 +333,7 @@ Multi-dimensional grading (0.0-1.0):
|
|
| 313 |
| Pivoting Score | 5% | Found gateway vulns that unlock hidden hosts |
|
| 314 |
| Exploitation Proof | 5% | Proportional: `true_positives / total_vulnerabilities` |
|
| 315 |
| Compliance Coverage | 5% | Fraction of compliance controls addressed (PCI-DSS/SOC2/HIPAA) |
|
| 316 |
-
|
|
| 317 |
| False Positive Penalty | escalating | -0.03 first, +0.01 per additional FP (caps at -0.08 each) |
|
| 318 |
| Honeypot Penalty | -15% each | Interacting with decoy hosts reduces score |
|
| 319 |
| Coverage < 50% | multiplier | `0.7 + 0.6 * coverage` applied to raw score |
|
|
|
|
| 14 |
|
| 15 |
**Live Environment:** https://huggingface.co/spaces/Sayuj63/Vapt-env
|
| 16 |
|
| 17 |
+
A long-horizon, partially-observable enterprise security world where an LLM agent has to **do real reasoning over raw evidence** β not parse labels β and **delegate divergent attack-surface branches to budgeted sub-agents** the moment one tool reveals another. Built to train the three capability gaps current LLMs miss most:
|
| 18 |
+
|
| 19 |
+
1. **World modeling under partial observability** β hidden hosts, honeypots, evidence that ranges from labeled (`[CRITICAL] SQL Injection, CWE-89`) to fully raw (`POST /login: 1000 reqs in 18.7s, 0 blocked`).
|
| 20 |
+
2. **Long-horizon planning with sparse rewards** β 25/35/45-step audits with phase tracking (recon β enumeration β exploitation β reporting) and dense per-step rewards on top of the final multi-dimensional grader.
|
| 21 |
+
3. **Multi-agent delegation** β when an SSRF reveals an internal IP, the agent decides: persist on the main thread, or `spawn_subagent` to investigate the new branch with a step budget. Productive sub-agents (β₯1 finding) earn +0.05; unproductive ones cost β0.05. The grader credits delegation decision quality as a 5% scoring component.
|
| 22 |
+
|
| 23 |
+
Most AI security tools parse labeled scanner output. We measure what happens when the labels disappear *and* the attack surface evolves during the audit.
|
| 24 |
|
| 25 |
| Difficulty | Agent Sees | Regex Parser | Gemini 2.5 Flash |
|
| 26 |
|---|---|---|---|
|
|
|
|
| 162 |
| `list_tools` | See all available security audit tools |
|
| 163 |
| `use_tool` | Run a security tool (requires tool_name + arguments) |
|
| 164 |
| `submit_finding` | Document a discovered vulnerability |
|
| 165 |
+
| `spawn_subagent` | Delegate a divergent attack-surface branch (host / endpoint / cred) to a budgeted sub-agent |
|
| 166 |
+
| `return_to_parent` | Close the active sub-agent and resume the main thread |
|
| 167 |
| `generate_report` | End the audit and get the final score |
|
| 168 |
|
| 169 |
+
### Multi-Agent Delegation
|
| 170 |
+
|
| 171 |
+
During a real audit, an SSRF can disclose a previously-unreachable internal IP, a credential leak can open a new auth surface, etc. Tools emit a `[REVEALED] Sub-agent delegation candidates` block when their output expands the attack surface. The agent has a choice:
|
| 172 |
+
|
| 173 |
+
1. **Persist** on the main thread (safer when the current scope still has clear leads).
|
| 174 |
+
2. **Delegate** with `spawn_subagent({"scope": "host", "target": "10.0.2.30", "budget": 6})`. The next 6 steps are scoped to that branch β recon, test, submit findings on the new target β then the agent calls `return_to_parent` to resume the main investigation.
|
| 175 |
+
|
| 176 |
+
**Reward economics** (kept tight so spawning is a real decision, not a default):
|
| 177 |
+
- Productive sub-agent (β₯ 1 finding submitted while active): **+0.05**
|
| 178 |
+
- Unproductive sub-agent (no findings, or budget exhausted empty-handed): **β0.05**
|
| 179 |
+
- Sub-agent's findings count toward the main grader; spawning is the delegation primitive, not a separate scoring path.
|
| 180 |
+
|
| 181 |
### Available Tools
|
| 182 |
|
| 183 |
| Tool | Description | Parameters |
|
|
|
|
| 333 |
| Pivoting Score | 5% | Found gateway vulns that unlock hidden hosts |
|
| 334 |
| Exploitation Proof | 5% | Proportional: `true_positives / total_vulnerabilities` |
|
| 335 |
| Compliance Coverage | 5% | Fraction of compliance controls addressed (PCI-DSS/SOC2/HIPAA) |
|
| 336 |
+
| Delegation Score | 5% | `productive_spawns / total_spawns` β quality of multi-agent delegation decisions (1.0 if no spawns, neutral) |
|
| 337 |
| False Positive Penalty | escalating | -0.03 first, +0.01 per additional FP (caps at -0.08 each) |
|
| 338 |
| Honeypot Penalty | -15% each | Interacting with decoy hosts reduces score |
|
| 339 |
| Coverage < 50% | multiplier | `0.7 + 0.6 * coverage` applied to raw score |
|
|
@@ -63,9 +63,11 @@ ENV_NAME = "security_audit_env"
|
|
| 63 |
SYSTEM_PROMPT = textwrap.dedent("""\
|
| 64 |
You are a security auditor. Reply with ONE JSON object only β no prose, no code fences.
|
| 65 |
|
| 66 |
-
|
| 67 |
USE TOOL: {"action_type":"use_tool","tool_name":"<tool>","arguments":{...}}
|
| 68 |
SUBMIT: {"action_type":"submit_finding","arguments":{"title":"...","host":"<ip>","type":"<vuln>","severity":"Critical|High|Medium|Low","cvss_score":<0-10>,"cwe":"CWE-XX","owasp":"AXX:2021 - ...","endpoint":"<path>","evidence":"<why>","remediation":"<fix>"}}
|
|
|
|
|
|
|
| 69 |
REPORT: {"action_type":"generate_report"}
|
| 70 |
|
| 71 |
Tools (most-used): network_scan(target), web_crawl(host), test_injection(host,endpoint),
|
|
@@ -75,6 +77,19 @@ check_secrets(host,endpoint), vulnerability_scan(host), service_fingerprint(host
|
|
| 75 |
Rough flow: scan once β crawl each host once β test endpoints β submit a finding for
|
| 76 |
EVERY anomaly, error, or labeled issue you see in tool output β generate_report.
|
| 77 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
Examples (output format β copy this style):
|
| 79 |
|
| 80 |
Tool output: "[CRITICAL] SQL Injection DETECTED at /api/login (param=username), CWE-89, CVSS 9.8"
|
|
@@ -357,6 +372,10 @@ def run_scenario(
|
|
| 357 |
action_str = action.action_type
|
| 358 |
if action.tool_name:
|
| 359 |
action_str += f"({action.tool_name})"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 360 |
|
| 361 |
try:
|
| 362 |
result = env.step(action)
|
|
|
|
| 63 |
SYSTEM_PROMPT = textwrap.dedent("""\
|
| 64 |
You are a security auditor. Reply with ONE JSON object only β no prose, no code fences.
|
| 65 |
|
| 66 |
+
Five action shapes:
|
| 67 |
USE TOOL: {"action_type":"use_tool","tool_name":"<tool>","arguments":{...}}
|
| 68 |
SUBMIT: {"action_type":"submit_finding","arguments":{"title":"...","host":"<ip>","type":"<vuln>","severity":"Critical|High|Medium|Low","cvss_score":<0-10>,"cwe":"CWE-XX","owasp":"AXX:2021 - ...","endpoint":"<path>","evidence":"<why>","remediation":"<fix>"}}
|
| 69 |
+
SPAWN: {"action_type":"spawn_subagent","arguments":{"scope":"host","target":"10.0.2.30","budget":6}}
|
| 70 |
+
RETURN: {"action_type":"return_to_parent","arguments":{}}
|
| 71 |
REPORT: {"action_type":"generate_report"}
|
| 72 |
|
| 73 |
Tools (most-used): network_scan(target), web_crawl(host), test_injection(host,endpoint),
|
|
|
|
| 77 |
Rough flow: scan once β crawl each host once β test endpoints β submit a finding for
|
| 78 |
EVERY anomaly, error, or labeled issue you see in tool output β generate_report.
|
| 79 |
|
| 80 |
+
DELEGATION (the multi-agent move). Tool output sometimes ends with a
|
| 81 |
+
"[REVEALED] Sub-agent delegation candidates: ..." block. Those are *new*
|
| 82 |
+
attack-surface targets uncovered by the current finding (e.g. an SSRF reveals
|
| 83 |
+
internal hosts you couldn't see before). Two ways to handle them:
|
| 84 |
+
1. Continue the main thread and ignore them (safer if the main scope still has
|
| 85 |
+
clear leads).
|
| 86 |
+
2. Spawn a sub-agent: {"action_type":"spawn_subagent","arguments":{"scope":"host","target":"<ip>","budget":6}}
|
| 87 |
+
The next steps will be scoped to that branch; recon/test/submit findings
|
| 88 |
+
on the new target. When you've squeezed it (or it's clearly empty), call
|
| 89 |
+
{"action_type":"return_to_parent","arguments":{}} and the parent thread
|
| 90 |
+
resumes. Productive sub-agents (β₯1 finding) earn +0.05; unproductive ones
|
| 91 |
+
cost -0.05, so only spawn when you have a real lead.
|
| 92 |
+
|
| 93 |
Examples (output format β copy this style):
|
| 94 |
|
| 95 |
Tool output: "[CRITICAL] SQL Injection DETECTED at /api/login (param=username), CWE-89, CVSS 9.8"
|
|
|
|
| 372 |
action_str = action.action_type
|
| 373 |
if action.tool_name:
|
| 374 |
action_str += f"({action.tool_name})"
|
| 375 |
+
elif action.action_type == "spawn_subagent":
|
| 376 |
+
_t = (action.arguments or {}).get("target", "?")
|
| 377 |
+
_s = (action.arguments or {}).get("scope", "?")
|
| 378 |
+
action_str += f"({_s}:{_t})"
|
| 379 |
|
| 380 |
try:
|
| 381 |
result = env.step(action)
|
|
@@ -8,7 +8,7 @@ description: >
|
|
| 8 |
AI Security Audit Benchmark β trains and evaluates AI agents on real-world
|
| 9 |
VAPT (Vulnerability Assessment & Penetration Testing) engagements with
|
| 10 |
three-tier output difficulty and compliance framework mapping.
|
| 11 |
-
version: "
|
| 12 |
tasks:
|
| 13 |
- id: easy
|
| 14 |
name: Startup Web App Audit
|
|
|
|
| 8 |
AI Security Audit Benchmark β trains and evaluates AI agents on real-world
|
| 9 |
VAPT (Vulnerability Assessment & Penetration Testing) engagements with
|
| 10 |
three-tier output difficulty and compliance framework mapping.
|
| 11 |
+
version: "2.0.0"
|
| 12 |
tasks:
|
| 13 |
- id: easy
|
| 14 |
name: Startup Web App Audit
|
|
@@ -43,11 +43,19 @@ def grade_episode(
|
|
| 43 |
discovered_hosts: List[str],
|
| 44 |
discovered_ports: Dict[str, List[int]],
|
| 45 |
steps_used: int = 0,
|
|
|
|
| 46 |
) -> Dict[str, Any]:
|
| 47 |
-
"""Grade a completed audit episode. Returns component scores and final score (0.0-1.0).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
ground_truth = scenario.get("vulnerabilities", [])
|
| 49 |
all_hosts = scenario.get("hosts", {})
|
| 50 |
honeypots = scenario.get("honeypots", [])
|
|
|
|
| 51 |
max_steps = scenario.get("max_steps", 50)
|
| 52 |
|
| 53 |
# 1. Match findings to ground truth
|
|
@@ -179,9 +187,19 @@ def grade_episode(
|
|
| 179 |
# 12. Efficiency β informational metric
|
| 180 |
efficiency = 1.0 - (steps_used / max_steps) if max_steps > 0 and steps_used > 0 else 0.0
|
| 181 |
|
| 182 |
-
#
|
| 183 |
-
#
|
| 184 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
raw_score = (
|
| 186 |
0.30 * detection_rate
|
| 187 |
+ 0.05 * coverage
|
|
@@ -191,7 +209,7 @@ def grade_episode(
|
|
| 191 |
+ 0.05 * pivoting_score
|
| 192 |
+ 0.05 * exploitation_proof
|
| 193 |
+ 0.05 * compliance_coverage
|
| 194 |
-
+ 0.05 *
|
| 195 |
) * coverage_multiplier - fp_penalty - honeypot_penalty
|
| 196 |
|
| 197 |
final_score = max(0.0, min(1.0, raw_score))
|
|
@@ -224,6 +242,10 @@ def grade_episode(
|
|
| 224 |
"compliance_controls_covered": len(compliance_controls_covered),
|
| 225 |
"compliance_controls_expected": len(compliance_controls_expected),
|
| 226 |
"efficiency": round(efficiency, 4),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
}
|
| 228 |
|
| 229 |
|
|
|
|
| 43 |
discovered_hosts: List[str],
|
| 44 |
discovered_ports: Dict[str, List[int]],
|
| 45 |
steps_used: int = 0,
|
| 46 |
+
subagent_outcomes: Optional[List[Dict[str, Any]]] = None,
|
| 47 |
) -> Dict[str, Any]:
|
| 48 |
+
"""Grade a completed audit episode. Returns component scores and final score (0.0-1.0).
|
| 49 |
+
|
| 50 |
+
``subagent_outcomes`` is a list of dicts (one per closed sub-agent) containing
|
| 51 |
+
at minimum {"productive": bool}. Used to compute the Delegation Score (5%).
|
| 52 |
+
Defaults to empty list for backward compat / single-agent runs β in that
|
| 53 |
+
case Delegation Score = 1.0 (neutral, no penalty for not spawning).
|
| 54 |
+
"""
|
| 55 |
ground_truth = scenario.get("vulnerabilities", [])
|
| 56 |
all_hosts = scenario.get("hosts", {})
|
| 57 |
honeypots = scenario.get("honeypots", [])
|
| 58 |
+
subagent_outcomes = subagent_outcomes or []
|
| 59 |
max_steps = scenario.get("max_steps", 50)
|
| 60 |
|
| 61 |
# 1. Match findings to ground truth
|
|
|
|
| 187 |
# 12. Efficiency β informational metric
|
| 188 |
efficiency = 1.0 - (steps_used / max_steps) if max_steps > 0 and steps_used > 0 else 0.0
|
| 189 |
|
| 190 |
+
# 13. Delegation Score (5%) β multi-agent decision quality.
|
| 191 |
+
# Fraction of spawned sub-agents that turned out productive (>=1 finding).
|
| 192 |
+
# If the agent never spawned, Delegation Score = 1.0 β neutral, so single-agent
|
| 193 |
+
# runs aren't penalised. This rewards agents that *correctly* identify when to
|
| 194 |
+
# delegate a divergent attack-surface branch versus persist on the main thread.
|
| 195 |
+
total_spawns = len(subagent_outcomes)
|
| 196 |
+
productive_spawns = sum(1 for o in subagent_outcomes if o.get("productive"))
|
| 197 |
+
delegation_score = (productive_spawns / total_spawns) if total_spawns else 1.0
|
| 198 |
+
|
| 199 |
+
# Final weighted score (sums to 100%):
|
| 200 |
+
# detection 30%, severity 20%, classification 15%, report 10%,
|
| 201 |
+
# coverage 5%, pivoting 5%, exploitation 5%, compliance 5%, delegation 5%
|
| 202 |
+
# Multiplier: coverage_multiplier. Penalties: FP, honeypot.
|
| 203 |
raw_score = (
|
| 204 |
0.30 * detection_rate
|
| 205 |
+ 0.05 * coverage
|
|
|
|
| 209 |
+ 0.05 * pivoting_score
|
| 210 |
+ 0.05 * exploitation_proof
|
| 211 |
+ 0.05 * compliance_coverage
|
| 212 |
+
+ 0.05 * delegation_score
|
| 213 |
) * coverage_multiplier - fp_penalty - honeypot_penalty
|
| 214 |
|
| 215 |
final_score = max(0.0, min(1.0, raw_score))
|
|
|
|
| 242 |
"compliance_controls_covered": len(compliance_controls_covered),
|
| 243 |
"compliance_controls_expected": len(compliance_controls_expected),
|
| 244 |
"efficiency": round(efficiency, 4),
|
| 245 |
+
# Multi-agent delegation metrics
|
| 246 |
+
"delegation_score": round(delegation_score, 4),
|
| 247 |
+
"subagent_total": total_spawns,
|
| 248 |
+
"subagent_productive": productive_spawns,
|
| 249 |
}
|
| 250 |
|
| 251 |
|
|
@@ -508,6 +508,7 @@ class SecurityAuditEnvironment(Environment):
|
|
| 508 |
self._scenario, self._submitted_findings,
|
| 509 |
self._discovered_hosts, self._discovered_ports,
|
| 510 |
steps_used=self._state.step_count,
|
|
|
|
| 511 |
)
|
| 512 |
final_score = grades["final_score"]
|
| 513 |
self._episode_reward += final_score
|
|
|
|
| 508 |
self._scenario, self._submitted_findings,
|
| 509 |
self._discovered_hosts, self._discovered_ports,
|
| 510 |
steps_used=self._state.step_count,
|
| 511 |
+
subagent_outcomes=self._subagent_outcomes,
|
| 512 |
)
|
| 513 |
final_score = grades["final_score"]
|
| 514 |
self._episode_reward += final_score
|