Spaces:
Sleeping
Sleeping
| 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. | |