Peer Pressure: Execution-Verified Co-Evolution of Attacker and Defender Populations for Code Security
Organizations: AWS AI
Abstract
LLM code agents increasingly ship code with little oversight, yet defenders trained on static vulnerability datasets fall behind adapting adversaries. We co-train attacker and defender instead, and let execution decide every reward: an attacker (Red) edits a real repository and earns nothing unless its patch applies, builds, passes the tests, completes its development task and reproduces a real exploit in an isolated container, whose path also labels the entry point, the critical operation and the trace between them. Peer-Relative Policy Optimization (PRPO) makes both sides populations of LoRA adapters over one frozen base and credits each against same-role peers that faced an identical condition, placing the population inside the policy-gradient baseline rather than the opponent pool, with a tournament preserving diversity. Matching cancels the condition's difficulty: at equal coverage it is worth 2.3 points of balanced accuracy over a baseline computed on other conditions, and it matches a within-policy control at half the verifier spend. PRPO's 7B diff reviewer reaches 64.8% balanced accuracy on four unseen applications in three untrained languages, 5.5 points above supervised security fine-tuning and 4.4 above a frozen frontier reviewer (Claude Opus 4.8), which it leads by 15.8 on the application it trains on. Its 9B repository agent recovers 23 of 172 held-out VulnGym advisories, against 10 after supervised fine-tuning and 7 for the same backbone untrained under the same scaffold, and locates 17 of 43 reachable entry points against 13 for mini-swe-agent with GLM-5.2. Run unchanged on jailbreak defense, the same algorithm matches or exceeds WildGuard's static F1 at equal corpus and model size. Under a judge distinct from its training oracle it cuts attack success from 36.0% to 4.6%, against WildGuard's 8.9%.
Figures & tables
| Variant (matched train. budget) | isolates | Src | Unseen | (paired) |
| Base (zero-shot) | — | 61.4 | ||
| Security-SFT only | no RL | 74.0 | ||
| Single pair ( both) | population | 81.0 | ||
| Ensemble-of-4 (no co-train) | capacity | 83.2 | ||
| Within-policy RLOO ( ) | allocation | 83.8 | ||
| baseline swapped, population/reward/training budget/surrogate fixed | ||||
| Easy ( adv., entries) | Medium ( , ) | Hard ( , ) | |||||||
| Detector | AR | ER | ES | AR | ER | ES | AR | ER | ES |
| published, Claude Code (Qwen tiers) and OpenHands (DeepSeek) ( Ji et al., 2026 ) | |||||||||
| Qwen3.5-9B | 6.4 | 4.3 | 2.5 | 3.2 | 1.8 | 1.7 | 0.0 | 0.0 | 0.0 |
| Qwen3.5-27B | — | — | — | — | — | — | 12.9 | 8.7 | 4.6 |
| DeepSeek-V4-Flash | — | — | — | — | — | — | 22.6 | 15.2 | 11.6 |
| ours, Qwen3.5-9B under mini-swe-agent, repository-level, matched training budget | |||||||||
| Guard | ASR (WG) | ASR (LG3) | XSTest refusal |
| No guard | 36.0 | 37.1 | 2.1 |
| WildGuard (7B, released) | 8.9 | 9.6 | 4.2 |
| Safety-SFT guard | 9.2 | 9.9 | 3.5 |
| Single pair | 8.2 | 8.9 | 3.7 |
| Within-policy RLOO ( ) | 6.1 | 6.8 | 3.8 |
| Population, global baseline | 5.9 | 6.5 | 3.9 |
Appendix figures & tables19 assets
Supplementary material from the paper’s appendix.
Appendix
| Role and setting | Receives, and is withheld |
| Red, both code arms | Certified site (pre-patch slice of one to three files), development task and target class. No search, verifier output or Blue verdict. |
| Blue, diff level | Unified diff and post-patch context. Returns a verdict and a confidence, no location. No label, exploit, tools or verifier output. |
| Blue, repository level | Checkout, a candidate advisory-style report (on a clean condition it describes a weakness considered but not introduced, so it never reveals the label) and read-only tools ( calls). Returns a verdict, a confidence and a located finding. No diff, site or exploit. |
| VulnGym | The benchmark’s checkout, description, scaffold and evaluator; never trained on. |
| Red, safety | One seed harmful behavior. No guard weights or classifier output. |
| Blue, safety | Two calls. Call 1 sees the prompt alone and returns the block probability that gates deployment; Call 2 sees the response and returns the response-level judgements, which enter only and the response tasks. No seed behavior or classifier label. |
| Application | Lang. | Role | Upstream repository |
| Grimoire | TS | train A, B | goniszewski/grimoire (v0.5.0) |
| Lago | Ruby | train B | getlago/lago |
| PrestaShop | PHP | train B | PrestaShop/PrestaShop |
| Pretix | Python | train B | pretix/pretix |
| OpenEMR | PHP | train B | openemr/openemr |
| Habitica | JS | train B | HabitRPG/habitica |
| Credit scheme | Adv. var. | Ep. tgt | Seed SD | Coll. | Bal. acc. |
| Global baseline ( ) | 1.00 | 4.73k | 2.1 | 3/12 | 85.4 |
| MPA ( , default) | 0.77 | 3.57k | 0.8 | 1/12 | 87.0 |
| MPA ( ) | 0.61 | 3.41k | 0.7 | 1/12 | 87.4 |
| WildGuardTest (human labels) | ASR | |||||
| Guard | prompt harm | resp. harm | resp. refusal | WG judge | LG3 judge | XSTest refusal |
| no guard, and published external guards (not budget-matched) | ||||||
| No guard | — | — | — | 36.0 | 37.1 | 2.1 |
| Llama Guard 3 (8B) | 68.4 | 63.1 | — | 11.2 | 11.8 | 4.9 |
| WildGuard (7B) | 88.9 | 75.4 | 88.6 | 8.9 | 9.6 | 4.2 |
| ours, matched training budget, trained on HarmBench WildJailbreak | ||||||
| Quantity | What it establishes | Jailbreak (code) |
| Red vs. frozen-initial Blue | attacker capability | ( ) |
| Red vs. current Blue | two-sidedness, bounded band | mean , slope n.s. |
| Blue vs. frozen-initial Red | defender capability | F 1 ( bal. acc.) |
| Peer correlation | is the estimator in regime? | CI ( ) |
| Predicted variance ratio | ( ) | |
| Measured advantage variance | does the prediction hold? | , ( , ) |
| Window | Vuln/Clean | TP | TN | FP | FN | Evasion |
| Rounds 1–6 | 2 / 4 | 1 | 3 | 1 | 1 | 0.50 |
| Rounds 7–12 | 4 / 2 | 3 | 2 | 0 | 1 | 0.25 |
| Rounds 13–18 | 4 / 2 | 1 | 2 | 0 | 3 | 0.75 |
| Rounds 19–24 | 3 / 3 | 1 | 1 | 2 | 2 | 0.67 |
| Rounds 25–30 | 4 / 2 | 2 | 2 | 0 | 2 | 0.50 |
| Rounds 31–36 | 3 / 3 | 3 | 2 | 1 | 0 | 0.00 |
| Component | Count |
| Total source files | 152 |
| API route handlers | 41 |
| Database modules (Drizzle ORM) | 21 |
| Svelte UI components | 58 |
| Authentication handlers | 2 |
| Configuration files | 8 |
| CWE | Name | Tier | Grimoire attack surface |
| CWE-89 | SQL Injection | High | Drizzle ORM queries, 6 repositories |
| CWE-79 | Cross-Site Scripting | High | @html directives in Svelte components |
| CWE-78 | OS Command Injection | High | Server-side utility scripts |
| CWE-22 | Path Traversal | Medium | File upload and bookmark import |
| CWE-862 | Missing Authorization | Medium | Route guards on state-changing endpoints |
| CWE-918 | Server-Side Request Forgery | Medium | Bookmark URL fetching |
| T2: EP identification | T3: CO localization | T4: trace construction | |||||||
| Detector | FR | LR | FP | FR | LR | FP | ES | TNR | TNP |
| published frontier coding agents holding the column maxima ( Ji et al., 2026 ) | |||||||||
| mini-swe-agent, GLM-5.2 | 30.2 | 18.6 | 11.1 | 39.5 | 18.6 | 2.5 | 36.4 | 49.5 | 23.3 |
| mini-swe-agent, DeepSeek-V4-Flash | 16.3 | 7.0 | 3.5 | 51.2 | 16.3 | 1.6 | 27.8 | 37.7 | 16.1 |
| Claude Code, GLM-5.2 | 25.6 | 9.3 | 6.0 | 48.8 | 30.2 | 5.2 | 32.1 | 39.9 | 19.8 |
| Claude Code, DeepSeek-V4-Flash | 14.0 | 4.7 | 1.7 | 34.9 | 16.3 | 0.9 | 34.7 | 54.0 | 20.2 |
| Method | Grim. (src) | Mini. | Book. | File. | Plaus. | Mean unseen |
| Security SFT | 74.0 | 59.4 | 58.6 | 62.3 | 56.8 | 59.3 |
| Single-pair R++ | 81.0 | 63.6 | 60.5 | 63.6 | 57.9 | 61.4 |
| Ensemble-of-4 | 83.2 | 64.5 | 62.2 | 63.8 | 58.3 | 62.2 |
| Within-policy RLOO | 83.8 | 63.0 | 64.2 | 64.3 | 58.1 | 62.4 |
| same population, reward, budget and surrogate; baseline swapped | ||||||
| learned critic (GAE) | 82.6 | 62.8 | 62.4 | 65.0 | 57.8 | 62.0 |
| # | Result | Backbone | Training data | Evaluation set | Metric |
| 1 | Co-evolution vs. static (§ 5 ) | Qw2.5-Coder-7B , diff | Setting A | src. held-out; unseen apps, paired | bal. acc. |
| 2 | VulnGym T1 (Table 2 ) | Qwen3.5-9B , repo | Setting A, B | T1; adv., entries | AR, ER, ES |
| 3 | VulnGym T2–T4 (Table A9 ) | Qwen3.5-9B , repo | Setting A, B | T2–T4, Hard; reference targets | FR, LR, FP; ES, TNR, TNP |
| 4 | Transfer and composition (§ 5 ) | Qw2.5-Coder-7B , diff | Setting A vs. B | unseen apps, paired | bal. acc.; TPR@FPR |
| 5 | Co-evolution dynamics (Fig. 2 ) | Qw2.5-Coder-7B , diff | Setting A | frozen-initial and current opponent | evasion; bal. acc.; |
| 6 | Component ablations (App. Y ) | Qw2.5-Coder-7B , diff | Setting A | unseen apps, paired | bal. acc. |
| Control | What it tests | Result (bal. acc. % unless noted) |
| co-evolution endpoints (§ 4 ) | ||
| Held-out external attacker | holds up vs. an attacker outside the loop | absolute; not paired with SFT’s |
| Frozen frontier reviewer | is a capable model enough on its own? | src / vs. ; unseen / vs. |
| Freeze Red, train Blue | does the opponent drive gains? | vs ( ) |
| Freeze Blue, train Red | symmetric check | evasion , then flat |
| Historical Blue robustness | no forgetting on the defence | catches of earlier evasions |
| arm | diffs per condition | adapters per condition | samples per adapter | distinct conds. per adapter |
| MPA ( ) | 2 | 2 | 500 | 500 |
| within-policy RLOO ( ) | 2 | 1 | 500 | 250 |
| RLOO, generations | 2 | 1 | 1000 | 500 |
| global baseline ( ) | 1 | 1 | 500 | 500 |
AI Agent Security
Artificial intelligence agent security studies attacks and safeguards for tool-using, persistent, and collaborative systems across digital and physical settings.
See all 510 papers