Spaces:
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
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:
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:
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)
- Create a new Space β Docker SDK.
- Push this folder (
CyberArena/) to the Space's git remote:cd CyberArena git remote add hf https://huggingface.co/spaces/Alpha-Team/CyberArena git push hf main - In the Space's Settings β Repository secrets, add every key
the backend needs. HF injects them as environment variables
(the loader in
app/_env.pydoes not override process env, so secrets take priority over any.envshipped in the repo).Secret Required SUPABASE_URLyes SUPABASE_ANON_KEYyes CLOUDFLARE_API_TOKENyes (primary AI) CLOUDFLARE_ACCOUNT_IDyes CLOUDFLARE_MODELoptional (default @cf/qwen/qwen2.5-coder-32b-instruct)GROQ_API_KEYyes (fallback AI) GROQ_MODELoptional (default llama-3.1-8b-instant)NVIDIA_API_KEYyes (tertiary AI) NVIDIA_MODELoptional (default deepseek-ai/deepseek-v4-pro)MISTRAL_API_KEYyes (quaternary AI + vuln-hunter grader) MISTRAL_MODELoptional (default mistral-small-latest) - The Space URL will be
https://<user>-<space>.hf.space. The frontend'sVITE_API_URLmust be set to that URL +/api(e.g.https://alpha-team-cyberarena.hf.space/api). - Workdir caveat: per-challenge user scripts (
solve.py, β¦) live intempfile.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. - 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
.envfile is never uploaded β it's listed in.dockerignore. The HF secrets panel is the only source of environment variables in production.