CystronCode commited on
Commit
9556146
·
1 Parent(s): 8722538

initial deploy — jairaj files, teammate files pending

Browse files
Dockerfile ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+ WORKDIR /app
3
+ COPY . .
4
+ RUN pip install openenv flask fastapi uvicorn httpx gradio requests bandit
5
+ EXPOSE 7860
6
+ CMD ["openenv", "run", "--port", "7860"]
README.md CHANGED
@@ -1,10 +0,0 @@
1
- ---
2
- title: Cannon And Wall
3
- emoji: 😻
4
- colorFrom: green
5
- colorTo: blue
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
__pycache__/openenv.cpython-313.pyc ADDED
Binary file (1.43 kB). View file
 
agents/__init__.py ADDED
File without changes
agents/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (175 Bytes). View file
 
agents/__pycache__/cannon_prompt.cpython-313.pyc ADDED
Binary file (733 Bytes). View file
 
agents/__pycache__/wall_prompt.cpython-313.pyc ADDED
Binary file (691 Bytes). View file
 
agents/cannon_prompt.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ def build_cannon_prompt(source_code: str, stage: int, round: int) -> str:
2
+ return f"""You are a Red Team security analyst. Stage {stage}, Round {round}.
3
+ Analyze this source code and find ONE vulnerability.
4
+ Respond ONLY in this exact JSON format:
5
+ {{"agent":"cannon","vuln_type":"sqli|xss|broken_auth","line_number":0,"explanation":"...","proof_of_concept":"..."}}
6
+
7
+ SOURCE CODE:
8
+ {source_code}"""
agents/wall_prompt.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ def build_wall_prompt(source_code: str, cannon_report: dict, stage: int) -> str:
2
+ return f"""You are a Blue Team security engineer. Stage {stage}.
3
+ Red team found: {cannon_report}
4
+ Patch the vulnerability. Respond ONLY in this exact JSON format:
5
+ {{"agent":"wall","patched_code":"...full fixed file...","explanation":"..."}}
6
+
7
+ SOURCE CODE:
8
+ {source_code}"""
client/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # client package
client/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (175 Bytes). View file
 
client/__pycache__/client.cpython-313.pyc ADDED
Binary file (1.63 kB). View file
 
client/client.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import httpx
2
+
3
+ class CannonWallClient:
4
+ def __init__(self, base_url: str = "http://localhost:7860"):
5
+ self.base_url = base_url
6
+
7
+ def reset(self, stage: int = 1) -> dict:
8
+ r = httpx.post(f"{self.base_url}/reset", params={"stage": stage})
9
+ return r.json()
10
+
11
+ def step(self, action: dict) -> dict:
12
+ r = httpx.post(f"{self.base_url}/step", json=action)
13
+ return r.json()
14
+
15
+ def state(self) -> dict:
16
+ r = httpx.get(f"{self.base_url}/state")
17
+ return r.json()
environment/__pycache__/curriculum.cpython-313.pyc ADDED
Binary file (1.21 kB). View file
 
environment/__pycache__/models.cpython-313.pyc ADDED
Binary file (1.29 kB). View file
 
environment/__pycache__/server.cpython-313.pyc ADDED
Binary file (2.54 kB). View file
 
environment/curriculum.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+
3
+ STAGES = {
4
+ 1: {"file": "environment/vulnerable_app/stage_1/app.py",
5
+ "vulns": ["sqli"], "description": "Single SQLi in login form"},
6
+ 2: {"file": "environment/vulnerable_app/stage_1/app.py",
7
+ "vulns": ["xss"], "description": "Single XSS in comment box"},
8
+ 3: {"file": "environment/vulnerable_app/stage_1/app.py",
9
+ "vulns": ["sqli", "xss", "broken_auth"], "description": "All three vulns"}
10
+ }
11
+
12
+ def next_stage(current: int, scores: dict) -> int:
13
+ if scores.get("wall", 0) > 8:
14
+ return min(current + 1, max(STAGES.keys()))
15
+ return current
16
+
17
+ def get_stage_source(stage: int) -> str:
18
+ return Path(STAGES[stage]["file"]).read_text()
environment/judge/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # judge package
environment/judge/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (186 Bytes). View file
 
environment/judge/__pycache__/reward.cpython-313.pyc ADDED
Binary file (2.85 kB). View file
 
environment/judge/__pycache__/verifier.cpython-313.pyc ADDED
Binary file (6.17 kB). View file
 
environment/server.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from openenv import Environment
2
+ from pathlib import Path
3
+ from environment.models import Observation
4
+ from environment.judge.verifier import verify_cannon, verify_wall
5
+ from environment.judge.reward import cannon_reward, wall_reward
6
+
7
+ STAGE_FILES = {
8
+ 1: "environment/vulnerable_app/stage_1/app.py"
9
+ }
10
+ GROUND_TRUTH = {
11
+ 1: {"vuln_type": "sqli", "line_number_range": [10, 20]}
12
+ }
13
+
14
+ class CannonWallEnvironment(Environment):
15
+ def reset(self, stage: int = 1):
16
+ source = Path(STAGE_FILES[stage]).read_text()
17
+ self.state = {
18
+ "round": 1,
19
+ "stage": stage,
20
+ "source_code": source,
21
+ "scores": {"cannon": 0.0, "wall": 0.0},
22
+ "last_cannon_report": None
23
+ }
24
+ return self.state
25
+
26
+ def step(self, action: dict):
27
+ agent = action.get("agent")
28
+ if agent == "cannon":
29
+ v = verify_cannon(action)
30
+ if not v["valid"]:
31
+ return {"reward": 0, "issues": v["issues"]}
32
+ r = cannon_reward(action, GROUND_TRUTH[self.state["stage"]])
33
+ self.state["scores"]["cannon"] += r
34
+ self.state["last_cannon_report"] = action
35
+ return {"reward": r, "valid": True}
36
+ elif agent == "wall":
37
+ test_results = verify_wall(
38
+ action.get("patched_code", ""),
39
+ self.state["stage"]
40
+ )
41
+ r = wall_reward(action.get("patched_code",""), [], test_results)
42
+ self.state["scores"]["wall"] += r
43
+ self.state["round"] += 1
44
+ return {"reward": r, "test_results": test_results}
45
+ return {"error": "unknown agent"}
environment/vulnerable_app/stage_1/app.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # stage_1/app.py
2
+ # Cannon & Wall -- Vulnerable Target App (Stage 1)
3
+ # WARNING: This file is intentionally vulnerable for security training purposes.
4
+ # DO NOT deploy this in production.
5
+
6
+ from flask import Flask, request, session, jsonify
7
+
8
+ app = Flask(__name__)
9
+ app.secret_key = "hardcoded_secret_123"
10
+
11
+ # Lines 10-20: SQLi vulnerability lives here
12
+ @app.route("/login", methods=["POST"])
13
+ def login():
14
+ username = request.form.get("username", "")
15
+ password = request.form.get("password", "")
16
+ # VULN: sqli -- string interpolation directly into SQL query
17
+ query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
18
+ # Attacker input: ' OR 1=1-- bypasses this check entirely
19
+ users = {"admin": "password123", "user1": "secret"}
20
+ if username in users and users[username] == password:
21
+ session["user"] = username
22
+ return jsonify({"status": "ok", "user": username})
23
+ return jsonify({"status": "fail"}), 401
24
+
25
+ # Lines 25-35: XSS vulnerability lives here
26
+ @app.route("/comments", methods=["GET"])
27
+ def comments():
28
+ comment = request.args.get("comment", "")
29
+ # VULN: xss -- user input rendered directly into HTML without escaping
30
+ return f"<html><body><div>{comment}</div></body></html>"
31
+ # Attacker input: <script>alert('xss')</script> executes in browser
32
+
33
+
34
+
35
+ # blank line above (line 35)
36
+ # blank line above (line 36)
37
+ # blank line above (line 37)
38
+ # blank line above (line 38)
39
+ # Lines 40-55: Broken Auth vulnerability lives here
40
+ @app.route("/dashboard", methods=["GET"])
41
+ def dashboard():
42
+ # VULN: broken_auth -- session user is set from request without server validation
43
+ if request.args.get("user"):
44
+ session["user"] = request.args.get("user")
45
+ user = session.get("user")
46
+ if not user:
47
+ return jsonify({"status": "unauthorized"}), 401
48
+ return jsonify({"status": "ok", "dashboard": f"Welcome {user}"})
49
+
50
+ @app.route("/health")
51
+ def health():
52
+ return jsonify({"status": "running"})
53
+
54
+ if __name__ == "__main__":
55
+ app.run(debug=False, port=5001)
openenv.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Local shim for the OpenEnv Environment base class.
3
+ The PyPI `openenv` package (v0.1.x) exposes only openenv.env.Env
4
+ and does not include an `Environment` class. This module provides
5
+ the abstract base that CannonWallEnvironment subclasses, matching
6
+ the interface declared in openenv.yaml.
7
+ """
8
+
9
+ class Environment:
10
+ """Base class for OpenEnv-compatible environments."""
11
+
12
+ def __init__(self):
13
+ self.state = {}
14
+
15
+ def reset(self, **kwargs) -> dict:
16
+ raise NotImplementedError
17
+
18
+ def step(self, action: dict) -> dict:
19
+ raise NotImplementedError
20
+
21
+ def get_state(self) -> dict:
22
+ return self.state
openenv.yaml ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: cannon-and-wall
2
+ version: 1.0.0
3
+ author: Jairaj S
4
+ theme: self-improvement+multi-agent
5
+ base_class: Environment
6
+ entry: environment/server.py
7
+ client: client/client.py
8
+ port: 7860
9
+ tags:
10
+ - security
11
+ - red-team
12
+ - self-play
13
+ - rl
training/train_grpo.ipynb ADDED
File without changes
ui/demo.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from client.client import CannonWallClient
3
+
4
+ client = CannonWallClient()
5
+
6
+ def start_round(stage):
7
+ obs = client.reset(stage=int(stage))
8
+ return obs["source_code"], "Round started. Source loaded.", ""
9
+
10
+ def get_scores():
11
+ s = client.state()
12
+ scores = s.get("scores", {})
13
+ return f"Cannon: {scores.get('cannon',0)} | Wall: {scores.get('wall',0)} | Round: {s.get('round',1)}"
14
+
15
+ with gr.Blocks() as demo:
16
+ with gr.Tab("Run Episode"):
17
+ stage_dd = gr.Dropdown([1,2,3], value=1, label="Stage")
18
+ start_btn = gr.Button("Start Round")
19
+ source_box = gr.Textbox(label="Source Code", lines=20)
20
+ status_box = gr.Textbox(label="Status")
21
+ output_box = gr.Textbox(label="Agent Output")
22
+ start_btn.click(start_round, inputs=[stage_dd], outputs=[source_box, status_box, output_box])
23
+ with gr.Tab("Scores"):
24
+ score_btn = gr.Button("Refresh Scores")
25
+ score_box = gr.Textbox(label="Current Scores")
26
+ score_btn.click(get_scores, outputs=[score_box])
27
+
28
+ if __name__ == "__main__":
29
+ demo.launch(server_port=7861)