readme: project description, results, training logs, links
Browse files
README.md
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: "Janus (AdaptShield): Adaptive Incident Response Under Polymorphic Adversaries"
|
| 3 |
+
emoji: π‘οΈ
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: red
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
license: mit
|
| 9 |
+
tags:
|
| 10 |
+
- openenv
|
| 11 |
+
- security
|
| 12 |
+
- reinforcement-learning
|
| 13 |
+
- cybersecurity
|
| 14 |
+
short_description: Two-phase adaptive cybersecurity benchmark for LLMs
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
# Janus (AdaptShield): Adaptive Incident Response Under Polymorphic Adversaries
|
| 18 |
+
|
| 19 |
+
**AdaptShield** is the environment: a two-phase agentic cybersecurity
|
| 20 |
+
simulator where an LLM defends a 4-node enterprise network against an
|
| 21 |
+
adversary that shifts strategy mid-episode. **Janus** is the model we
|
| 22 |
+
trained on it: a Qwen2.5-1.5B LoRA, supervised then refined with GRPO.
|
| 23 |
+
On the hardest task Janus scores 0.90 on a held-out world family it
|
| 24 |
+
never saw during training; a tool-aware heuristic baseline scores 0.18
|
| 25 |
+
on the same task.
|
| 26 |
+
|
| 27 |
+
The skill being tested is narrow on purpose. Not threat classification.
|
| 28 |
+
Not generic tool calling. The benchmark targets one thing: real-time
|
| 29 |
+
adaptation when the attacker's playbook changes mid-incident. Section
|
| 30 |
+
[Why this matters](#why-this-matters) explains why we think that's the
|
| 31 |
+
gap, and the [Results](#results) section is where the gap closes.
|
| 32 |
+
|
| 33 |
+
## Project Links
|
| 34 |
+
|
| 35 |
+
- **HF Space (live env):** [`SaiManish123/adaptshield`](https://huggingface.co/spaces/SaiManish123/adaptshield)
|
| 36 |
+
- **Colab notebook (SFT + GRPO reproducer, free T4):** [`Project_Janus(AdaptShield)_Final.ipynb`](https://drive.google.com/file/d/1uI9BaQTsn8YXOAlCtQCr_0N6ixLbqlba/view?usp=sharing)
|
| 37 |
+
- **Artifacts / model repo:** [`SaiManish123/Janus`](https://huggingface.co/SaiManish123/Janus)
|
| 38 |
+
- **Demo video:** [`youtu.be/upX9a5zXHBM`](https://youtu.be/upX9a5zXHBM)
|
| 39 |
+
|
| 40 |
+
---
|
| 41 |
+
|
| 42 |
+
## Why this matters
|
| 43 |
+
|
| 44 |
+
Most cyber-agent demos test threat classification or generic tool
|
| 45 |
+
calling. Real production breaches don't look like that. They look like
|
| 46 |
+
this:
|
| 47 |
+
|
| 48 |
+
In April 2026 attackers compromised Context.ai, used its OAuth
|
| 49 |
+
integration into a Vercel employee's Google Workspace, and pivoted from
|
| 50 |
+
shadow AI through identity into Vercel's internal systems, where they
|
| 51 |
+
enumerated and decrypted customer environment variables. The same week,
|
| 52 |
+
a Broken Object Level Authorization flaw in Lovable.dev let any
|
| 53 |
+
free-tier account read source code, Supabase credentials, Stripe keys,
|
| 54 |
+
and AI chat histories from other tenants, including projects built by
|
| 55 |
+
AI itself. Eight months earlier, the Tea dating app left a Firebase
|
| 56 |
+
bucket open and 72,000 verification selfies and driver's licenses of
|
| 57 |
+
women on a safety app were scraped to 4chan within hours.
|
| 58 |
+
|
| 59 |
+
Three different failure modes, one underlying problem for the
|
| 60 |
+
defender's agent: identity hijack via shadow AI, broken authorization
|
| 61 |
+
in vibe-coded apps, and classic cloud misconfig. The environment is
|
| 62 |
+
shifting faster than any static training distribution can keep up with,
|
| 63 |
+
and the real attacker does not sit still while you classify them.
|
| 64 |
+
|
| 65 |
+
Real campaigns drift through the kill chain (initial access, lateral
|
| 66 |
+
movement, exfiltration) and the defender's job is to re-classify,
|
| 67 |
+
contain, and eradicate as the picture changes. Static SOAR playbooks
|
| 68 |
+
keyed to fixed indicators of compromise fail the moment the adversary
|
| 69 |
+
rotates them; that is what an attacker TTP shift looks like in
|
| 70 |
+
production, and it is the regime where dwell time blows out and Tier-1
|
| 71 |
+
triage starts dropping signal.
|
| 72 |
+
|
| 73 |
+
AdaptShield is built around that pressure. The environment forces the
|
| 74 |
+
agent to act on partial evidence, hand judgment across two roles with
|
| 75 |
+
an information bottleneck between them, trade security correctness
|
| 76 |
+
against operational blast radius, and re-plan when the attacker pivots
|
| 77 |
+
mid-incident. Each of those is a separate failure mode in production
|
| 78 |
+
SOC tooling, and the benchmark scores all four at once.
|
| 79 |
+
|
| 80 |
+
---
|
| 81 |
+
|
| 82 |
+
## Results
|
| 83 |
+
|
| 84 |
+
Numbers below come from the production run on Hugging Face L4 Jobs,
|
| 85 |
+
training Qwen2.5-1.5B-Instruct with a LoRA adapter. Eval is 50
|
| 86 |
+
deterministic seeds per task, evaluated on a held-out world family
|
| 87 |
+
the policy never saw during training.
|
| 88 |
+
|
| 89 |
+

|
| 90 |
+
|
| 91 |
+
On the hard task (`polymorphic-zero-day`) the tool-aware heuristic
|
| 92 |
+
baseline scores 0.18 and Janus holds 0.90 on the held-out family. On
|
| 93 |
+
the easier tasks the lift is smaller because the rule baseline is
|
| 94 |
+
already near the ceiling; the benchmark is shaped so adaptation only
|
| 95 |
+
matters where it should.
|
| 96 |
+
|
| 97 |
+
### Benchmark comparison (full table)
|
| 98 |
+
|
| 99 |
+
| Task | No-tool baseline | Tool-aware baseline | SFT (train family) | SFT (held-out) | GRPO (train) | GRPO (held-out) |
|
| 100 |
+
|------|-----------------:|-------------------:|-------------------:|---------------:|-------------:|----------------:|
|
| 101 |
+
| `direct-triage` | 0.860 | 0.990 | 0.990 | 0.990 | 0.990 | 0.990 |
|
| 102 |
+
| `dual-pivot` | 0.650 | 0.640 | 0.825 | 0.825 | 0.825 | 0.825 |
|
| 103 |
+
| `polymorphic-zero-day` | 0.380 | 0.180 | 0.960 | 0.930 | **0.883** | **0.902** |
|
| 104 |
+
|
| 105 |
+
Two things in this table are worth flagging.
|
| 106 |
+
|
| 107 |
+
The tool-aware baseline scores 0.18 on the hard task, worse than the
|
| 108 |
+
no-tool baseline at 0.38. That is not a bug in the baseline; it is
|
| 109 |
+
that bolting tools onto a heuristic without learning when to trust them
|
| 110 |
+
makes the agent over-trigger on injected false positives. You see the
|
| 111 |
+
same pattern in production with rule-based SOAR playbooks against
|
| 112 |
+
adaptive adversaries.
|
| 113 |
+
|
| 114 |
+
Held-out GRPO (0.902) actually edges out train-family GRPO (0.883). That
|
| 115 |
+
is evidence the policy is generalizing across world templates rather
|
| 116 |
+
than memorizing them. Without splitting the eval by world family this
|
| 117 |
+
finding would not be visible. Same-seed evaluation would have credited
|
| 118 |
+
the model for memorization it did not do.
|
| 119 |
+
|
| 120 |
+
### SFT: loss and held-out reward
|
| 121 |
+
|
| 122 |
+

|
| 123 |
+
|
| 124 |
+

|
| 125 |
+
|
| 126 |
+
### GRPO: refinement on the polymorphic adversary
|
| 127 |
+
|
| 128 |
+

|
| 129 |
+
|
| 130 |
+
### Training runs
|
| 131 |
+
|
| 132 |
+
Three production runs on Hugging Face Jobs produced the artifacts in this
|
| 133 |
+
README. Stdout logs are public and the per-step / per-episode metrics
|
| 134 |
+
files are next to the adapters.
|
| 135 |
+
|
| 136 |
+
| Run | Trainer | GPU | Steps / Episodes | Train wall-clock | Logs | Metrics |
|
| 137 |
+
|-----|---------|-----|------------------|------------------|------|---------|
|
| 138 |
+
| [`sft_worldsplit_1_5b`](https://huggingface.co/SaiManish123/Janus/tree/main/sft_worldsplit_1_5b) | SFT (LoRA) | L4 Γ1 | 378 steps | 9m 49s | [stdout](https://huggingface.co/SaiManish123/Janus/blob/main/logs/sft_worldsplit_1_5b.log) | [trainer_state](https://huggingface.co/SaiManish123/Janus/blob/main/sft_worldsplit_1_5b/checkpoint-378/trainer_state.json) |
|
| 139 |
+
| [`grpo_worldsplit_1_5b`](https://huggingface.co/SaiManish123/Janus/tree/main/grpo_worldsplit_1_5b) | GRPO, mixed curriculum | L4 Γ1 | 1,628 episodes | 1h 26m | [stdout](https://huggingface.co/SaiManish123/Janus/blob/main/logs/grpo_worldsplit_1_5b.log) | [per-episode](https://huggingface.co/SaiManish123/Janus/blob/main/grpo_worldsplit_1_5b/metrics.json) |
|
| 140 |
+
| [`grpo_polymorphic_zero_day_1_5b`](https://huggingface.co/SaiManish123/Janus/tree/main/grpo_polymorphic_zero_day_1_5b) | GRPO, hard-task focus | L4 Γ1 | 4,357 episodes | 3h 17m | [stdout](https://huggingface.co/SaiManish123/Janus/blob/main/logs/grpo_polymorphic_zero_day_1_5b.log) | [per-episode](https://huggingface.co/SaiManish123/Janus/blob/main/grpo_polymorphic_zero_day_1_5b/metrics.json) |
|
| 141 |
+
|
| 142 |
+
The curriculum run mixes all three tasks (weights `direct-triage: 0.3 /
|
| 143 |
+
dual-pivot: 0.4 / polymorphic-zero-day: 0.3`). The polymorphic run
|
| 144 |
+
trains exclusively on the hard task to push hard-task performance
|
| 145 |
+
without distraction from saturated tiers. Per-episode reward in both
|
| 146 |
+
runs stabilizes within the first ~500 episodes and stays there for the
|
| 147 |
+
rest of the schedule.
|
| 148 |
+
|
| 149 |
+
---
|
| 150 |
+
|
| 151 |
+
## Architecture
|
| 152 |
+
|
| 153 |
+

|
| 154 |
+
|
| 155 |
+
Each episode runs against a sampled mission profile, world-family
|
| 156 |
+
template, and latent operational mode. The Threat Analyst investigates
|
| 157 |
+
raw enterprise evidence through SOC tools and emits a structured
|
| 158 |
+
handoff. The Tactical Executor sees only that handoff (not the raw
|
| 159 |
+
state) and chooses the mitigation. The split mirrors the
|
| 160 |
+
Tier-1-to-Tier-2 escalation in a real SOC, where the responder acts on
|
| 161 |
+
the analyst's written triage and never re-examines the raw telemetry.
|
| 162 |
+
A deterministic Python grader scores security correctness, business
|
| 163 |
+
impact, dependency blast radius, and mission alignment. There is no
|
| 164 |
+
LLM-as-judge anywhere in the loop.
|
| 165 |
+
|
| 166 |
+
## Training Pipeline
|
| 167 |
+
|
| 168 |
+

|
| 169 |
+
|
| 170 |
+
Five steps, each reproducible from the repo:
|
| 171 |
+
|
| 172 |
+
1. Generate SFT demonstrations by rolling AdaptShield episodes with a
|
| 173 |
+
rule-based Phase 1 expert and a tool-aware Phase 2 expert.
|
| 174 |
+
2. Train a LoRA adapter on Qwen2.5-1.5B (or 0.5B for the Colab
|
| 175 |
+
reproducer) with supervised fine-tuning on those demos.
|
| 176 |
+
3. Evaluate on both train-family and held-out-family worlds. The split
|
| 177 |
+
is by world template, not by seed, so memorizing a template doesn't
|
| 178 |
+
transfer across the split.
|
| 179 |
+
4. Refine the SFT adapter with GRPO on a curriculum weighted toward
|
| 180 |
+
`polymorphic-zero-day`. The deterministic grader is the reward.
|
| 181 |
+
5. Publish adapters, curves, metrics, and benchmark tables to
|
| 182 |
+
[`SaiManish123/Janus`](https://huggingface.co/SaiManish123/Janus).
|
| 183 |
+
|
| 184 |
+
A free-tier Colab notebook reproduces steps 1-4 end-to-end on a T4 in
|
| 185 |
+
roughly 35 minutes using Qwen2.5-0.5B and reduced episode budgets. The
|
| 186 |
+
numbers in this README come from the 1.5B run on a Hugging Face L4 Job.
|
| 187 |
+
|
| 188 |
+
---
|
| 189 |
+
|
| 190 |
+
## Environment Description
|
| 191 |
+
|
| 192 |
+
The agent defends a 4-node enterprise network (`auth_service`,
|
| 193 |
+
`payment_service`, `database`, `api_gateway`). Each turn has two phases:
|
| 194 |
+
|
| 195 |
+
**Phase 1 (Threat Analyst).** Agent reads SIEM metrics, can call SOC
|
| 196 |
+
tools (log search, network telemetry, threat intel lookup), and emits a
|
| 197 |
+
structured `Phase1Action` with threat type, target node, confidence and
|
| 198 |
+
a recommended action.
|
| 199 |
+
|
| 200 |
+
**Phase 2 (Tactical Executor).** Agent receives only the Phase 1
|
| 201 |
+
assessment (blind to raw state) and emits a `Phase2Action`. The analyst
|
| 202 |
+
has to communicate clearly because the executor cannot double-check the
|
| 203 |
+
network.
|
| 204 |
+
|
| 205 |
+
The attacker escalates through `recon β exploit β exfiltration` if the
|
| 206 |
+
agent fails to respond correctly. On the hard task, the attacker shifts
|
| 207 |
+
strategy mid-episode and seeds false-positive noise that looks like a
|
| 208 |
+
real attack but isn't, which punishes reflexive isolation. This is the
|
| 209 |
+
alert-fatigue regime that drives most production SOC false-positive
|
| 210 |
+
budgets.
|
| 211 |
+
|
| 212 |
+
### Observation Space
|
| 213 |
+
|
| 214 |
+
```json
|
| 215 |
+
{
|
| 216 |
+
"phase": "1 or 2",
|
| 217 |
+
"network_nodes": {
|
| 218 |
+
"auth_service": {"status": "...", "request_rate": 0, "error_rate": 0.0, "cpu": 0}
|
| 219 |
+
},
|
| 220 |
+
"active_alerts": ["raw metric alert strings (no MITRE codes)"],
|
| 221 |
+
"attack_stage": "recon | exploit | exfiltration | none",
|
| 222 |
+
"history": [{"turn": "1", "p1": "classified:brute_force", "p2": "rate_limitβauth_service"}],
|
| 223 |
+
"phase1_assessment": {"threat_type": "...", "confidence": 0.9, "target_node": "..."},
|
| 224 |
+
"metadata": {"normalized_score": 0.72}
|
| 225 |
+
}
|
| 226 |
+
```
|
| 227 |
+
|
| 228 |
+
Phase 2 observations have empty `network_nodes` and `active_alerts`.
|
| 229 |
+
The executor only sees the analyst's handoff.
|
| 230 |
+
|
| 231 |
+
### Action Space
|
| 232 |
+
|
| 233 |
+
**Phase 1 (`Phase1Action`):**
|
| 234 |
+
```json
|
| 235 |
+
{"threat_type": "brute_force", "confidence": 0.9, "target_node": "auth_service", "recommended_action": "rate_limit", "reasoning": "..."}
|
| 236 |
+
```
|
| 237 |
+
|
| 238 |
+
**Phase 2 (`Phase2Action`):**
|
| 239 |
+
```json
|
| 240 |
+
{"action": "rate_limit", "target_node": "auth_service", "reasoning": "..."}
|
| 241 |
+
```
|
| 242 |
+
|
| 243 |
+
Valid actions: `rate_limit`, `isolate`, `honeypot`, `patch`, `monitor`.
|
| 244 |
+
|
| 245 |
+
### Tasks
|
| 246 |
+
|
| 247 |
+
| Task | Difficulty | Description | Rule baseline |
|
| 248 |
+
|------|-----------|-------------|--------------:|
|
| 249 |
+
| `direct-triage` | Easy | Single fixed strategy | ~0.87 |
|
| 250 |
+
| `dual-pivot` | Medium | Two alternating strategies | ~0.76 |
|
| 251 |
+
| `polymorphic-zero-day` | Hard | All four + mid-episode shift + noise | ~0.52 |
|
| 252 |
+
|
| 253 |
+
### Reward Function
|
| 254 |
+
|
| 255 |
+
| Outcome | Reward |
|
| 256 |
+
|---------|-------:|
|
| 257 |
+
| Phase 1 threat type correct | +0.15 |
|
| 258 |
+
| Phase 1 target node correct | +0.10 |
|
| 259 |
+
| Phase 2 optimal action + correct target | +0.39 |
|
| 260 |
+
| Phase 2 heavy-handed but effective | +0.18 |
|
| 261 |
+
| Phase 2 wrong action | -0.25 |
|
| 262 |
+
| False positive on benign event | -0.39 |
|
| 263 |
+
| Catastrophic: database exfiltrated | -0.49, `done=True` |
|
| 264 |
+
|
| 265 |
+
Scores are clipped to the open interval `(0.01, 0.99)`. The grader
|
| 266 |
+
never emits exactly 0 or 1, which keeps GRPO advantages well-defined.
|
| 267 |
+
|
| 268 |
+
### Operational Impact Layer
|
| 269 |
+
|
| 270 |
+
AdaptShield also scores business impact, so the agent is rewarded for
|
| 271 |
+
stopping the attack without ignoring operational blast radius. Each
|
| 272 |
+
service has a criticality weight and a dependency fan-out:
|
| 273 |
+
|
| 274 |
+
| Service | Criticality | Downstream dependency risk |
|
| 275 |
+
|---------|------------:|----------------------------|
|
| 276 |
+
| `auth_service` | 0.70 | `payment_service` |
|
| 277 |
+
| `payment_service` | 0.90 | `api_gateway` |
|
| 278 |
+
| `database` | 1.00 | `payment_service`, `api_gateway` |
|
| 279 |
+
| `api_gateway` | 0.80 | `auth_service`, `payment_service`, `database` |
|
| 280 |
+
|
| 281 |
+
Actions have bounded disruption costs (`monitor` = none, `isolate` =
|
| 282 |
+
highest). The grader emits `business_impact`, `availability_impact`,
|
| 283 |
+
`security_risk`, `dependency_blast_radius`, and `operational_penalty`
|
| 284 |
+
inside `score_breakdown`. The reward adjustment is capped at `Β±0.05` per
|
| 285 |
+
turn, which keeps the training signal stable while leaving the replay
|
| 286 |
+
detailed enough to explain whether the agent stopped the attack cleanly
|
| 287 |
+
or caused unnecessary business disruption getting there. This is the
|
| 288 |
+
MTTR-versus-availability tradeoff every SOC actually navigates:
|
| 289 |
+
containment that bricks `auth_service` to stop a credential-stuffing
|
| 290 |
+
campaign also takes legitimate users offline, so "isolate everything"
|
| 291 |
+
is not a winning playbook.
|
| 292 |
+
|
| 293 |
+
### Mission-Aware Objectives
|
| 294 |
+
|
| 295 |
+
Each task carries a mission profile, visible in observation metadata and
|
| 296 |
+
appended to the system prompt:
|
| 297 |
+
|
| 298 |
+
| Task | Mission | Primary Asset | SLA Priority | Risk Tolerance |
|
| 299 |
+
|------|---------|---------------|--------------|----------------|
|
| 300 |
+
| `direct-triage` | `login_stability` | `auth_service` | availability | medium |
|
| 301 |
+
| `dual-pivot` | `checkout_continuity` | `payment_service` | availability | medium |
|
| 302 |
+
| `polymorphic-zero-day` | `breach_containment` | `database` | containment | low |
|
| 303 |
+
|
| 304 |
+
The grader emits `mission_alignment` and `mission_adjustment`, capped at
|
| 305 |
+
`Β±0.04` per turn. This makes the agent optimize for the operational
|
| 306 |
+
mission, not just the threat label. Availability-priority missions
|
| 307 |
+
discourage unnecessary isolation of the primary asset; containment
|
| 308 |
+
missions reward decisive correct containment of the crown-jewel
|
| 309 |
+
database.
|
| 310 |
+
|
| 311 |
+
### Design choices that aren't obvious
|
| 312 |
+
|
| 313 |
+
A few decisions in the environment that look like details but matter
|
| 314 |
+
for what the benchmark actually measures:
|
| 315 |
+
|
| 316 |
+
- **Information bottleneck between phases.** Phase 2's observation has
|
| 317 |
+
empty `network_nodes` and `active_alerts`. The executor only sees
|
| 318 |
+
Phase 1's structured handoff. If Phase 1 cannot communicate clearly,
|
| 319 |
+
Phase 2 fails, and you see it in the score, not in a separate metric.
|
| 320 |
+
This is what makes the env actually test cross-role coordination
|
| 321 |
+
rather than just two independent policies stitched together.
|
| 322 |
+
- **Train/eval split by world family, not by seed.** The world templates
|
| 323 |
+
used for training are disjoint from the ones used for held-out
|
| 324 |
+
evaluation. A model that overfits to a specific service-name pattern
|
| 325 |
+
or a specific alert distribution will pass train evals and fail
|
| 326 |
+
held-out. Same-seed evaluation would have hidden this.
|
| 327 |
+
- **Open scoring interval `(0.01, 0.99)`.** The grader never emits
|
| 328 |
+
exactly 0 or 1. This keeps GRPO advantage estimates well-defined.
|
| 329 |
+
Saturating rewards collapse the variance the algorithm needs.
|
| 330 |
+
- **Bounded auxiliary signals.** Operational impact is capped at `Β±0.05`
|
| 331 |
+
per turn and mission alignment at `Β±0.04`. They steer the policy
|
| 332 |
+
without dominating the security signal, so the training curve does
|
| 333 |
+
not get hijacked by a single side-objective.
|
| 334 |
+
- **Deterministic Python grader, no LLM-as-judge.** Rewards come from
|
| 335 |
+
strategy matching against a fixed ground-truth attacker, not from a
|
| 336 |
+
judge model. The benchmark cannot be gamed by a more eloquent policy.
|
| 337 |
+
- **Phase-1 alerts are raw metric strings, not pre-tagged MITRE ATT&CK
|
| 338 |
+
techniques.** The agent has to do the classification itself, not
|
| 339 |
+
match a label to a label. This is what makes the heuristic baseline
|
| 340 |
+
collapse on the hard task: rule-based classification keyed on fixed
|
| 341 |
+
indicators of compromise does not survive the injected false-positive
|
| 342 |
+
noise that real polymorphic adversaries use to drown Tier-1 triage.
|
| 343 |
+
|
| 344 |
+
---
|
| 345 |
+
|
| 346 |
+
## Reproduce it
|
| 347 |
+
|
| 348 |
+
### Free-tier Colab (recommended for judges)
|
| 349 |
+
|
| 350 |
+
Open the Colab notebook linked above and run top-to-bottom. It will:
|
| 351 |
+
|
| 352 |
+
- install the exact pinned dependency stack used in the HF Job
|
| 353 |
+
- generate SFT demos from the environment
|
| 354 |
+
- train an SFT LoRA on Qwen2.5-0.5B (T4-friendly)
|
| 355 |
+
- run GRPO refinement on top of that SFT adapter
|
| 356 |
+
- print the benchmark table and inline the production training curves
|
| 357 |
+
from `SaiManish123/Janus` so you can compare scaled-down vs. full runs
|
| 358 |
+
|
| 359 |
+
End-to-end runtime on a Colab T4 is roughly 35 minutes.
|
| 360 |
+
|
| 361 |
+
### Local setup
|
| 362 |
+
|
| 363 |
+
```bash
|
| 364 |
+
pip install openenv-core
|
| 365 |
+
git clone https://github.com/SaiManish123/adaptshield
|
| 366 |
+
cd adaptshield
|
| 367 |
+
python -m adaptshield.server.app
|
| 368 |
+
```
|
| 369 |
+
|
| 370 |
+
### Run inference against the live environment
|
| 371 |
+
|
| 372 |
+
```bash
|
| 373 |
+
export HF_TOKEN=your_token
|
| 374 |
+
export ADAPTSHIELD_TASK=direct-triage # or dual-pivot / polymorphic-zero-day
|
| 375 |
+
export ENV_BASE_URL=http://localhost:7860
|
| 376 |
+
python inference.py # run from the repo root
|
| 377 |
+
```
|
| 378 |
+
|
| 379 |
+
`inference.py` honors the evaluator contract: `[START]`, `[STEP]`, `[END]`
|
| 380 |
+
stdout markers and credentials read only from environment variables.
|
| 381 |
+
|
| 382 |
+
### Smoke test
|
| 383 |
+
|
| 384 |
+
```bash
|
| 385 |
+
python smoke_test.py
|
| 386 |
+
```
|
| 387 |
+
|
| 388 |
+
Spins the env up in-process and walks one episode of each task with a
|
| 389 |
+
deterministic policy. Should finish in <10 seconds.
|
| 390 |
+
|
| 391 |
+
### Regression tests
|
| 392 |
+
|
| 393 |
+
```bash
|
| 394 |
+
adaptshield/.venv/bin/python -m unittest tests.test_regression -v
|
| 395 |
+
```
|
| 396 |
+
|
| 397 |
+
### Baseline scores
|
| 398 |
+
|
| 399 |
+
With `ADAPTSHIELD_SEED=42`, the deterministic rule baseline produces:
|
| 400 |
+
|
| 401 |
+
| Task | Score | Steps | Status |
|
| 402 |
+
|------|------:|------:|--------|
|
| 403 |
+
| `direct-triage` | 0.870 | 10 | PASS |
|
| 404 |
+
| `dual-pivot` | 0.760 | 12 | PASS |
|
| 405 |
+
| `polymorphic-zero-day` | 0.520 | 16 | PASS |
|
| 406 |
+
|
| 407 |
+
Difficulty staircase: **PASS**.
|
| 408 |
+
|
| 409 |
+
---
|
| 410 |
+
|
| 411 |
+
## Repository layout
|
| 412 |
+
|
| 413 |
+
```
|
| 414 |
+
adaptshield/
|
| 415 |
+
βββ server/ # FastAPI server (OpenEnv-compatible)
|
| 416 |
+
βββ client.py # OpenEnv client (no server-internal imports)
|
| 417 |
+
βββ models.py # Phase1Action / Phase2Action schemas
|
| 418 |
+
βββ soc_tools.py # SIEM, log search, threat intel SOC tools
|
| 419 |
+
βββ eval_tasks.py # task definitions + difficulty staircase
|
| 420 |
+
βββ baseline.py # deterministic rule baseline
|
| 421 |
+
βββ tool_baseline.py # tool-aware heuristic baseline
|
| 422 |
+
βββ generate_sft_data.py # rolls episodes β SFT JSONL
|
| 423 |
+
βββ train_sft.py # LoRA SFT trainer (Unsloth + TRL)
|
| 424 |
+
βββ train.py # GRPO trainer (Unsloth + TRL)
|
| 425 |
+
βββ plot_training.py # reward / loss curve plotting
|
| 426 |
+
βββ build_benchmark_table.py # eval matrix builder
|
| 427 |
+
βββ inference.py # judge-facing entry point
|
| 428 |
+
βββ smoke_test.py # one-shot in-process smoke test
|
| 429 |
+
βββ tests/test_regression.py # determinism + reward regression tests
|
| 430 |
+
βββ openenv.yaml # OpenEnv manifest
|
| 431 |
+
βββ Dockerfile # HF Space container
|
| 432 |
+
```
|
| 433 |
+
|
| 434 |
+
## Engineering notes
|
| 435 |
+
|
| 436 |
+
`AdaptShieldEnvironment` extends OpenEnv's `Environment` base class and
|
| 437 |
+
follows the Gym-style API (`reset`, `step`, `state`). The client in
|
| 438 |
+
`client.py` talks to the server only through HTTP, with no shared
|
| 439 |
+
imports and no leaking of server internals. None of the SOC tools are
|
| 440 |
+
named `reset`, `step`, `state`, or `close`, so they do not collide with
|
| 441 |
+
the reserved MCP tool names. Grading is deterministic Python; the
|
| 442 |
+
reward signal and the benchmark scores both come from strategy
|
| 443 |
+
matching against a fixed ground-truth attacker, never from an LLM
|
| 444 |
+
judge.
|
| 445 |
+
|
| 446 |
+
All adapters, curves, metrics, and benchmark tables for the 1.5B run
|
| 447 |
+
are public on [`SaiManish123/Janus`](https://huggingface.co/SaiManish123/Janus).
|
| 448 |
+
|
| 449 |
+
## License
|
| 450 |
+
|
| 451 |
+
MIT.
|