cannon-and-wall / README.md
CystronCode's picture
Update README.md
a9083ad verified
|
Raw
History Blame Contribute Delete
10.1 kB
metadata
title: Cannon And Wall
emoji: πŸ”΄πŸ”΅
colorFrom: red
colorTo: blue
sdk: docker
pinned: false

πŸ”΄πŸ”΅ Cannon & Wall β€” RedBlue Arena

A self-improving attacker vs defender RL environment for training LLMs on cybersecurity reasoning.

Author: Jairaj S | Hackathon: Meta PyTorch Γ— OpenEnv Γ— Scaler School of Technology, April 2026 Theme: Self-Improving Agents (Theme 4) + Multi-Agent Interactions (Theme 1)


πŸ”— Links

Resource URL
πŸ€— HuggingFace Space (live env) https://huggingface.co/spaces/CystronCode/cannon-and-wall
πŸŽ₯ Demo video (YouTube <2 min) https://youtu.be/E63MU02_1Y4
πŸ“ Blog writeup (this Space) Blog.md
πŸ““ Training notebook (Colab) https://colab.research.google.com/drive/1uTSt6DahNVXoAZ0hlpyzrXrn-F6ehc9z?usp=sharing
πŸ“ˆ Reward curves (W&B) https://wandb.ai/models-r-v-c-e/cannon-and-wall/runs/5v7yweib

🧠 The Problem

LLMs are surprisingly bad at security reasoning β€” not because they lack knowledge, but because they have never been trained to think adversarially in a loop.

Current benchmarks test security knowledge statically. There is no environment where:

  • An attacker agent gets smarter by failing to find bugs
  • A defender agent gets smarter by getting exploited
  • Both co-evolve through self-play, round after round

Cannon & Wall fills that gap.


🏟️ What Is the Environment?

A sandboxed Flask web application with seeded OWASP Top 3 vulnerabilities. Two LLM agents compete inside it:

Agent Role What it does
πŸ”΄ Cannon Attacker Reads source code, finds vulnerabilities, proposes exploits
πŸ”΅ Wall Defender Reads Cannon's report, patches the code, hardens the app
βš–οΈ Judge Verifier Runs deterministic checks β€” no LLM-as-judge

Vulnerability scope

  • SQL Injection (SQLi)
  • Cross-Site Scripting (XSS)
  • Broken Authentication

Key design decision β€” textual reasoning only

Neither agent executes live exploits. All reasoning happens over source code as text. This keeps the environment safe, reproducible, and trainable.


πŸ” The 3-Phase Self-Play Loop

Phase 1 β€” ATTACK
  Cannon reads the vulnerable Flask app source code
  Cannon outputs: { vuln_type, line_number, explanation, proof_of_concept }

Phase 2 β€” PATCH
  Wall reads the source code + Cannon's report
  Wall outputs: { patched_code, explanation }

Phase 3 β€” BYPASS
  Cannon reads original code + Wall's patched version
  Cannon tries to find a remaining vulnerability or bypass

Judge scores both agents after each bypass phase.
Loser faces a harder variant next round.
Both agents improve β€” neither can memorize.

πŸ† Reward Logic

# Cannon (Attacker)
+10   real vulnerability correctly identified
+5    correct vulnerability type (sqli / xss / broken_auth)
-5    false positive reported
+15   bypass succeeded (Wall's patch failed)

# Wall (Defender)
+5    per vulnerability correctly patched (up to 3)
+5    patched code still works (functionality preserved)
-5    patch introduced a new vulnerability
+5    bypass attempt failed (patch held)

# All rewards normalized to 0.0-1.0 for OpenEnv compliance
# Per-component rewards logged as separate W&B columns (rollout/attack_real_vuln_found, etc.)

πŸ“ Environment Structure

cannon-and-wall/
β”œβ”€β”€ openenv.yaml                          # OpenEnv manifest (v1.1 β€” action_space + observation_space)
β”œβ”€β”€ openenv.py                            # Environment base class (local stub)
β”œβ”€β”€ app.py                                # FastAPI wrapper (reset / step / state)
β”œβ”€β”€ Dockerfile                            # Container definition
β”œβ”€β”€ requirements.txt                      # Dependencies
β”‚
β”œβ”€β”€ environment/
β”‚   β”œβ”€β”€ server.py                         # CannonWallEnvironment class
β”‚   β”œβ”€β”€ models.py                         # Pydantic schemas
β”‚   β”œβ”€β”€ curriculum.py                     # Stage progression logic
β”‚   β”œβ”€β”€ vulnerable_app/
β”‚   β”‚   β”œβ”€β”€ stage_1/app.py               # Explicit SQLi + XSS + Broken Auth
β”‚   β”‚   β”œβ”€β”€ stage_2/app.py               # Split routes, aliased variables (medium)
β”‚   β”‚   └── stage_3/app.py               # Chained + obfuscated vulns (hard)
β”‚   └── judge/
β”‚       β”œβ”€β”€ verifier.py                   # AST-level SQLi check + bandit static analysis
β”‚       └── reward.py                     # Multi-component reward calculator
β”‚
β”œβ”€β”€ client/
β”‚   β”œβ”€β”€ client.py                         # CannonWallClient (httpx)
β”‚   └── models.py                         # Client-side Pydantic models
β”‚
β”œβ”€β”€ agents/
β”‚   β”œβ”€β”€ cannon_prompt.py                  # Red agent system prompt + helpers
β”‚   └── wall_prompt.py                    # Blue agent system prompt + helpers
β”‚
β”œβ”€β”€ training/
β”‚   └── train_grpo.ipynb                  # TRL + Unsloth GRPO training notebook
β”‚
└── ui/
    └── demo.py                           # Gradio live demo + leaderboard

πŸ›‘οΈ Anti-Reward-Hacking Measures

  • Judge is fully deterministic β€” AST-level checks + bandit static analysis (no LLM-as-judge)
  • Multiple independent reward components β€” gaming one does not help overall score
  • Proof-of-concept validation β€” Cannon must provide a working exploit pattern
  • Hard episode limit β€” MAX_ROUNDS=3, prevents infinite loops
  • Dangerous import rejection β€” patches with import os, exec(), eval() rejected instantly
  • Functionality preservation check β€” Wall must not break the app to score
  • AST-level SQLi verifier β€” parses patched code to confirm parameterized execute() is actually used, not just that ? appears in a comment

πŸŽ“ Curriculum

Stage File Task Vulnerabilities
1 stage_1/app.py Single-file login form SQLi + XSS + Broken Auth (explicit f-string)
2 stage_2/app.py Split /auth + /search routes SQLi (string concat) + XSS (aliased variable names)
3 stage_3/app.py Chained + obfuscated portal SQLi (string join) + XSS (inside href attribute) + Broken Auth (cookie override)

Escalation triggers when Wall's raw reward exceeds 8 for 3 consecutive episodes (agent is no longer challenged).


πŸ› οΈ Training Stack

Component Tool
RL algorithm GRPO via HuggingFace TRL
Efficiency layer transformers + peft + bitsandbytes (4-bit QLoRA)
Base model Qwen/Qwen2.5-3B-Instruct
Environment OpenEnv (Docker, HF Spaces)
Experiment tracking Weights & Biases (per-component reward columns)
Deployment HuggingFace Spaces (Docker)

πŸ”¬ Before vs After Training β€” Qualitative Example

Untrained (step 0) Trained (step 50)
vuln_type "xss" (wrong) "sqli" (correct)
line_number 5 (no vuln there) 16 (correct β€” f-string query)
proof_of_concept "<script>test</script>" "' OR 1=1-- "
Judge verdict False positive (βˆ’5 pts) Real vuln + correct type (+15 pts)
Cannon reward 0.000 (normalized) 0.571 (normalized)

The untrained model guesses XSS on the wrong line. After GRPO training it correctly identifies the SQLi on line 16 with a valid bypass PoC.


πŸ“ˆ Results

Reward curve β€” 50 GRPO gradient steps

Reward curve

X-axis: GRPO training step (each step = 4 completions sampled β†’ environment reward β†’ optimizer.step()). Y-axis: normalized reward (0 = worst, 1 = best). Dashed line: random-agent baseline (0.020) β€” random JSON with valid PoC patterns, no model reasoning. Cannon reward rises from near-zero baseline to ~0.57 within 50 steps β€” a 25Γ— improvement demonstrating real learning. Wall remains stable at ~0.71 throughout β€” it patches correctly regardless of Cannon's quality.

Quantitative results

Metric Value
Cannon reward at step 0 (random baseline) 0.020
Cannon reward at step 50 (GRPO trained) 0.570
Improvement over baseline 25Γ—
Wall reward (stable throughout) ~0.714
Wall patch validity rate 100%
GRPO gradient steps 50
Completions per step (G) 4
Base model Qwen/Qwen2.5-3B-Instruct
LoRA rank 16
Training time (Colab T4) ~35 min

Training note: Each step samples G=4 completions from the model, queries the live environment for rewards, computes group-relative advantages (r_i βˆ’ mean(r)), and calls optimizer.step() to update the LoRA adapter weights. This is real gradient-based learning β€” not an inference evaluation loop.


▢️ Running Locally

git clone https://huggingface.co/spaces/CystronCode/cannon-and-wall
cd cannon-and-wall
pip install -r requirements.txt
python app.py

# Test stage routing
curl -X POST "http://localhost:7860/reset?stage=1"   # loads stage_1/app.py
curl -X POST "http://localhost:7860/reset?stage=2"   # loads stage_2/app.py (split routes)
curl -X POST "http://localhost:7860/reset?stage=3"   # loads stage_3/app.py (chained vulns)

πŸ”Œ API Endpoints

Method Endpoint Description
POST /reset?stage=1 Start new episode, returns source code
POST /step Send agent action, returns reward + observation
GET /state Read current episode state
GET /docs FastAPI Swagger UI

πŸ’‘ Why This Matters

Security is one of the few domains where verification is fully objective, self-play is naturally adversarial, and the task is genuinely hard for current LLMs.

A model trained in Cannon & Wall learns to reason about code vulnerabilities through adversarial rounds β€” not through static examples. The AST-level verifier and multi-component reward make it hard to game without actually improving security reasoning.


πŸ“„ License

MIT


Built for the Meta PyTorch x OpenEnv Hackathon, Scaler School of Technology, Bangalore β€” April 2026