File size: 9,819 Bytes
73b272a
 
 
 
 
 
 
 
 
 
 
80a4a65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
04fc815
 
 
 
80a4a65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73b272a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
---
title: CyberArena Backend
emoji: πŸ›‘οΈ
colorFrom: purple
colorTo: blue
sdk: docker
app_port: 7860
pinned: false
license: mit
---

# CyberArena β€” Backend

> FastAPI backend for the CyberArena cybersecurity training platform.
> Single source of truth: this directory. The old `Apex/backend/` has
> been removed.

## Quick start

```bash
cd CyberArena
pip install -r requirements.txt
python main.py        # serves on http://localhost:8090
```

The server loads `.env` automatically from this folder.

## Project layout

```
CyberArena/
β”œβ”€β”€ main.py                        ← 5-line uvicorn entry
β”œβ”€β”€ requirements.txt
β”œβ”€β”€ Dockerfile
β”œβ”€β”€ start.cmd
β”‚
β”œβ”€β”€ app/                           ← all real code
β”‚   β”œβ”€β”€ main.py                    ← FastAPI app, middleware, startup
β”‚   β”œβ”€β”€ types.py                   ← Pydantic request models
β”‚   β”‚
β”‚   β”œβ”€β”€ core/                      ← constants + pure helpers (no I/O)
β”‚   β”‚   β”œβ”€β”€ config.py              ← env-loaded handles (Supabase, AI URLs, keys)
β”‚   β”‚   β”œβ”€β”€ constants.py           ← topic catalog, fallback HTML, cert config
β”‚   β”‚   β”œβ”€β”€ security.py            ← normalize_vuln_key, ip/ioc/time matchers
β”‚   β”‚   β”œβ”€β”€ text.py                ← parse_json_safe (LLM JSON parser)
β”‚   β”‚   └── module_router.py       ← module β†’ challenge_type dispatcher
β”‚   β”‚
β”‚   β”œβ”€β”€ services/                  ← business logic / I/O
β”‚   β”‚   β”œβ”€β”€ supabase_service.py    ← PostgREST client (CRUD on pool tables)
β”‚   β”‚   β”œβ”€β”€ challenge_loader.py    ← row β†’ TrainingData mappers (per type)
β”‚   β”‚   β”œβ”€β”€ scenario_service.py    ← Groq scenario + challenge generation
β”‚   β”‚   β”œβ”€β”€ evaluator.py           ← /evaluate handlers (5 types)
β”‚   β”‚   β”œβ”€β”€ completion_service.py  ← user_completions idempotent tracker
β”‚   β”‚   β”œβ”€β”€ certificate_service.py ← PDF build + cert issue/verify/download
β”‚   β”‚   └── file_storage.py        ← write downloadable files to disk
β”‚   β”‚
β”‚   β”œβ”€β”€ generators/                ← per-type AI pool generators
β”‚   β”‚   β”œβ”€β”€ __init__.py            ← REGISTRY: list of (slug, module, teams)
β”‚   β”‚   β”œβ”€β”€ crypto.py              ← owns encryption_challenges
β”‚   β”‚   β”œβ”€β”€ code_fixing.py         ← owns code_fixing_challenges
β”‚   β”‚   β”œβ”€β”€ log_analysis.py        ← owns log_analysis_challenges
β”‚   β”‚   └── vulnerability_hunter.py ← owns vulnerability_hunter_challenges
β”‚   β”‚
β”‚   β”œβ”€β”€ sandbox/                   ← in-process OS simulator sandbox
β”‚   β”‚   β”œβ”€β”€ workdir.py             ← per-challenge tempdir + safe_join
β”‚   β”‚   β”œβ”€β”€ tools.py               ← built-in cat/ls/sha256sum/base64/…
β”‚   β”‚   └── terminal.py            ← shlex parse + whitelist subprocess run
β”‚   β”‚
β”‚   └── api/                       ← FastAPI routers (HTTP shell only)
β”‚       β”œβ”€β”€ auth.py                ← /api/auth
β”‚       β”œβ”€β”€ xp.py                  ← /api/xp
β”‚       β”œβ”€β”€ leaderboard.py         ← /api/leaderboard
β”‚       β”œβ”€β”€ certificates.py        ← /api/certificates*
β”‚       β”œβ”€β”€ training.py            ← /api/training/*
β”‚       β”œβ”€β”€ terminal.py            ← /api/training/terminal*
β”‚       └── onevone.py             ← /api/onevone/* (head-to-head matches)
β”‚
β”œβ”€β”€ db/                            ← all database concerns
β”‚   β”œβ”€β”€ schema/                    ← numbered, idempotent SQL migrations
β”‚   β”‚   β”œβ”€β”€ 000_extensions.sql
β”‚   β”‚   β”œβ”€β”€ 001_users.sql
β”‚   β”‚   β”œβ”€β”€ 002_encryption_challenges.sql
β”‚   β”‚   β”œβ”€β”€ 004_code_fixing_challenges.sql
β”‚   β”‚   β”œβ”€β”€ 005_log_analysis_challenges.sql
β”‚   β”‚   β”œβ”€β”€ 006_vulnerability_hunter_challenges.sql
β”‚   β”‚   β”œβ”€β”€ 007_onevone.sql
β”‚   β”‚   β”œβ”€β”€ 008_certificates.sql
β”‚   β”‚   β”œβ”€β”€ 009_user_completions.sql
β”‚   β”‚   β”œβ”€β”€ 010_challenge_files_bucket.sql
β”‚   β”‚   β”œβ”€β”€ 011_challenge_type_normalization.sql
β”‚   β”‚   └── 012_onevone_vuln_hunter.sql
β”‚   β”œβ”€β”€ seed/                      ← per-type example JSON (template)
β”‚   β”‚   β”œβ”€β”€ README.md
β”‚   β”‚   β”œβ”€β”€ crypto.example.json
β”‚   β”‚   β”œβ”€β”€ web.example.json
β”‚   β”‚   β”œβ”€β”€ code_fixing.example.json
β”‚   β”‚   β”œβ”€β”€ log_analysis.example.json
β”‚   β”‚   └── vulnerability_hunter.example.json
β”‚   β”œβ”€β”€ apply.py                   ← apply every schema/*.sql in order
β”‚   └── README.md
β”‚
β”œβ”€β”€ scripts/                       ← ops utilities
β”‚   β”œβ”€β”€ check_state.py             ← one-page DB health report
β”‚   └── seed_pools.py              ← pool size + status (read-only by default)
β”‚
β”œβ”€β”€ .env                           ← secrets (never commit)
└── README.md

> **No local file storage.** AI-generated challenge files are uploaded
> to the `challenge-files` Supabase Storage bucket (see
> `db/schema/010_challenge_files_bucket.sql`) and the public URL is
> returned in the training payload. The backend never writes to its
> own disk.
```

## API surface

The FastAPI app exposes **28 routes** under `/api/`. The full list
lives in `app/main.py`; the most important are:

| Method | Path                                | Notes |
|--------|-------------------------------------|-------|
| POST   | `/api/auth`                         | proxied to Supabase `apex-auth` edge function |
| POST   | `/api/xp`                           | proxied to Supabase `apex-xp` edge function |
| GET    | `/api/leaderboard`                  | top users by XP |
| GET    | `/api/training/list`                | dashboard cards |
| POST   | `/api/training/generate`            | hydrate cached scenario into a full challenge |
| POST   | `/api/training/evaluate`            | legacy red+blue AI evaluator |
| POST   | `/api/training/evaluate-web`        | 3-layer web exploitation validator |
| POST   | `/api/training/evaluate-code-fix`   | AI code-fix grader |
| POST   | `/api/training/evaluate-log-analysis` | 4-field exact match + Mistral feedback |
| POST   | `/api/training/evaluate-vulnerability-hunter` | exact canonical-key match |
| POST   | `/api/training/terminal*`           | OS simulator sandbox |
| POST   | `/api/certificates`                 | list / issue |
| GET    | `/api/certificates/{id}/pdf`        | streamed PDF |
| GET    | `/api/certificates/verify/{code}`   | public QR landing |
| GET    | `/api/certificates/progress`        | (completions, required) |
| POST   | `/api/onevone/rooms`                | 1v1 mode (see `app/api/onevone.py`) |

## Pool architecture

Each challenge type owns its own table + its own generator. The
orchestrator in `app/main.py::populate_pool_background` walks
`app.generators.REGISTRY` and starts one watcher per (type, team).

Pool model: `POOL_TARGET / POOL_THRESHOLD / POOL_BATCH` are module
constants inside each generator. The watcher refills the table
when the count drops to `POOL_THRESHOLD` and inserts `POOL_BATCH` new
rows in parallel via `asyncio.gather`.

See `../AGENTS.md` "Pool Architecture" for the full contract.

## Database

Apply the migrations to Supabase with:

```bash
python db/apply.py          # needs SUPABASE_SERVICE_KEY in .env
```

Each `.sql` file is idempotent (`CREATE … IF NOT EXISTS`, `DROP
POLICY IF EXISTS + CREATE`). See `db/README.md` for details.

## Running tests

There is no formal test suite yet. Use:

```bash
python -c "from app.main import app; print(len(app.routes), 'routes')"
python scripts/check_state.py     # requires SUPABASE_URL
```

## Deployment

The provided `Dockerfile` pins `python:3.11-slim` and starts the
server on `PORT` (default 7860, matches Hugging Face Spaces).

### Hugging Face Spaces (Docker SDK)

1. Create a new Space β†’ **Docker** SDK.
2. Push this folder (`CyberArena/`) to the Space's git remote:
   ```bash
   cd CyberArena
   git remote add hf https://huggingface.co/spaces/Alpha-Team/CyberArena
   git push hf main
   ```
3. In the Space's **Settings β†’ Repository secrets**, add every key
   the backend needs. HF injects them as environment variables
   (the loader in `app/_env.py` does **not** override process env,
   so secrets take priority over any `.env` shipped in the repo).
   | Secret | Required |
   |---|---|
   | `SUPABASE_URL` | yes |
   | `SUPABASE_ANON_KEY` | yes |
   | `CLOUDFLARE_API_TOKEN` | yes (primary AI) |
   | `CLOUDFLARE_ACCOUNT_ID` | yes |
   | `CLOUDFLARE_MODEL` | optional (default `@cf/qwen/qwen2.5-coder-32b-instruct`) |
   | `GROQ_API_KEY` | yes (fallback AI) |
   | `GROQ_MODEL` | optional (default `llama-3.1-8b-instant`) |
   | `NVIDIA_API_KEY` | yes (tertiary AI) |
   | `NVIDIA_MODEL` | optional (default `deepseek-ai/deepseek-v4-pro`) |
   | `MISTRAL_API_KEY` | yes (quaternary AI + vuln-hunter grader) |
   | `MISTRAL_MODEL` | optional (default `mistral-small-latest`) |
4. The Space URL will be `https://<user>-<space>.hf.space`. The
   frontend's `VITE_API_URL` must be set to that URL + `/api`
   (e.g. `https://alpha-team-cyberarena.hf.space/api`).
5. **Workdir caveat**: per-challenge user scripts (`solve.py`, …)
   live in `tempfile.mkdtemp()` on the container's `/tmp`. The
   container's disk is **lost on Space sleep / restart**, but
   challenge data and user XP live in Supabase and persist.
6. The Space can sleep after 48h of inactivity. To stay always-on,
   upgrade the Space's hardware tier or wake it manually before
   class.

> The `.env` file is **never** uploaded β€” it's listed in
> `.dockerignore`. The HF secrets panel is the only source of
> environment variables in production.