Spaces:
Sleeping
Sleeping
| 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](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 | |
| ```python | |
| # 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 | |
|  | |
| *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 | |
| ```bash | |
| 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* |