Skip to content

Repository files navigation

CyberForge

A local-first platform for building, running and grading containerised security labs — on your own machine.

License: GPL v3 Runs offline Backend tests Frontend tests

CyberForge is the tooling layer you run yourself to define, provision, isolate, snapshot, monitor and tear down container-based lab environments — plus everything needed to turn a lab into an exercise: a written brief, a self-check that grades it, a catalog of well-known vulnerable targets, capture-the-flag mechanics, an in-browser attacker terminal, and a request workbench for poking at a target.

It is not a hosted training site and not a clone of one. Everything runs locally. Nothing is sent anywhere — no telemetry, no remote update check, not even to validate a form field.


Quick start

git clone <your-repo> cyberforge && cd cyberforge
cp .env.example .env
docker compose up -d

Open http://localhost:3000 and sign in with the bootstrap administrator from your .env (admin / ChangeMe!2024 by default).

Change that password immediately. The default is published in .env.example, so it is public knowledge.

Add the observability stack when you want it:

docker compose --profile monitoring up -d     # Prometheus, Grafana, Loki, Promtail
Service URL Notes
Web UI http://localhost:3000 The application
API docs http://localhost:8000/docs Swagger UI (hidden in production)
MinIO console http://localhost:9001 Object-storage browser
Grafana http://localhost:3001 monitoring profile only
Prometheus http://localhost:9090 monitoring profile only

Prefer a terminal? The bundled CLI is standard-library-only Python:

pip install ./sdk/python
cyberforge login --username admin
cyberforge template ls --tag dvwa
cyberforge lab new web/dvwa --name "My DVWA" --start
cyberforge lab verify "My DVWA"

What it does

Labs

A lab is a named, versioned set of containers on one or more isolated networks, with a resource budget and a lifecycle. Create one from scratch, from a built-in template, from the vulnerable-target catalog, by cloning, or by importing a portable .cyberforge.json export from another machine.

Lifecycle verbs are enforced by a server-side state machine, and the API tells the client which ones are currently legal so the UI never offers an action that would fail:

Action What it does What it frees
Start Provisions networks and containers, starts services
Sleep Pauses containers CPU. Memory and in-container state kept
Resume Unpauses containers
Stop Stops and removes containers Memory. Volumes are kept
Archive Releases all runtime resources, makes lab read-only Everything

That sleep/stop distinction is the point of the platform: sleeping a lab costs nothing in CPU and gives it back instantly, exactly as you left it.

Turning a lab into an exercise

  • Vulnerable-target catalog — one-click DVWA, Juice Shop, WebGoat, bWAPP and Metasploitable 2, plus a one-click Kali attacker box, all under stable slugs that external content can link. Every target ships on an internal network with no published port; exposing one is a decision you make, never one the catalog makes for you.
  • Guided labs — a lab can carry a Markdown brief and a declarative success check (http, tcp, flag, or container). POST /labs/{id}/verify runs the check from inside the lab's own network, so an isolated target is still gradeable, and returns a three-way verdict: passed, failed, or unverifiable — because "the check is malformed" is not the same as "you got it wrong".
  • ATT&CK coverage — templates are tagged with MITRE ATT&CK technique ids (never names — names drift between releases, ids do not), with a coverage endpoint that reports how many templates carry no mapping, so the number is honest rather than flattering.
  • Challenges, flags & scoreboard — a separate Challenge entity (a flag never lives on a Lab or Template, where it would leak), per-player flag instances injected as files, constant-time comparison, a two-layer submission rate limit, and a scoreboard whose freeze is a read-time filter and whose dynamic scores are computed, never stored.
  • Payload workbench — craft an HTTP request against a lab service, fire it from inside the lab network, see the response, tweak and replay, and save named variants beside a seeded, editable payload library (SQLi, XSS, command injection, traversal, SSTI).

Building and reaching things

  • Image build — write a Dockerfile in the browser and build an image, with a byte-offset streaming log. Off by default and gated three ways, because a RUN executes as root on the daemon host — the most privileged thing the platform can be asked to do.
  • Interactive terminal — a real pty into a lab container (xterm.js), with optional session recording and replay (output only by default; input recording is opt-in with a warning, since it captures typed passwords). Authorised by a single-use, short-lived, HTTP-minted ticket.
  • Exposure — publish a single service on a routed network for a time-boxed window, audited and reaped automatically. The backend itself never joins a lab network.
  • Live updates — the UI stays current over Server-Sent Events that carry invalidation hints, not data: the client is told what changed and refetches through the already-authorised REST endpoint, so nothing can leak over the stream that the endpoint would not have returned anyway.

The rest of the platform

  • Networking — virtual networks with real isolation. internal networks have no route to the host or the internet (the default). Subnet overlap is rejected. Firewall rules are stored declaratively and labelled honestly as either enforced or advisory. A topology view shows what is attached to what.
  • Containers — a read-through view of the Docker engine annotated with which lab owns what, plus logs, stats and lifecycle control.
  • Snapshots — capture a lab's definition and restore it later, with an automatic safety snapshot taken first. Volume contents are captured only when an operator has enabled it and the individual snapshot asks for it — and the snapshot's manifest always states exactly what it captured.
  • Storage — MinIO-backed object store with a filesystem fallback, indexed in PostgreSQL so listing and quota accounting never require a bucket scan.
  • Reports — Markdown, HTML and JSON rendered from one document model so they can never disagree; HTML output is self-contained and opens offline, and is linkable at a stable .../report.html URL.
  • Plugins — a real SDK. Plugins contribute API routes, dashboard widgets, frontend pages, themes and lab templates.
  • Enterprise — MFA, SSO, teams, per-field quotas, custom roles, webhooks, a PostgreSQL-backed job queue, image scanning and engagement tracking.
  • Monitoring — Prometheus metrics, a provisioned Grafana dashboard, and container logs shipped to Loki.
  • Audit — every security-relevant action recorded in a hash-chained, append-only trail.

Design commitments

These are the constraints the codebase is built around, not aspirations.

Runs on one machine, offline. No telemetry, no remote update check, no external call to validate a form field. GET /system/updates reports check_performed: false and says so.

Low idle footprint. The backend idles at roughly 120 MB RSS. There is no background poller: resource samples are computed on demand and memoised for a few seconds, so an idle server does no work at all. Historical recording is opt-in.

Only PostgreSQL is required. Redis falls back to an in-process cache, MinIO falls back to filesystem storage, and Docker being unavailable is a normal state — reads return empty collections and writes return 503 with an actionable message. The platform boots and the UI renders honestly in all of those cases.

No AI, deliberately. CyberForge ships zero AI functionality: no LLM client, no embeddings, no vector store, no RAG, no agents, no speech or vision. What exists is the seam — Protocols in app/services/ai/interfaces.py and a capability-discovery endpoint that reports enabled: false. Adding a provider later means writing one class; no existing call site changes.

Honest about limits. Where something is scaffolded rather than finished, the API and the UI say so. A snapshot's manifest records exactly which of the definition, the volume contents, and the container filesystem it captured. A verification whose check is malformed returns unverifiable, never a false failure. Disabling a plugin returns restart_required: true, because mounted routes genuinely cannot be unmounted at runtime.

The dangerous operations are gated and audited. Container exec, image build, and volume capture each default to off, require an explicit permission, and write an audit record. The hardened Docker-socket-proxy profile denies image build unless the same flag is set, so the two layers cannot silently disagree.


Architecture

Browser ──► nginx ──► FastAPI ──► PostgreSQL      (required)
                          ├────► Redis           (optional, falls back to memory)
                          ├────► MinIO           (optional, falls back to filesystem)
                          └────► Docker engine   (optional, degrades gracefully)

The backend is layered strictly inward — api → services → repositories → models — with adapters (Docker, storage, cache) behind Protocols so every external dependency has a working fallback. Nothing above app/services/docker/ imports docker; the engine, the null adapter and the Protocol change together.

apps/backend/app/
  core/          config, security, RBAC, errors, logging, metrics, middleware
  db/            engine, base, migrations, seed data, the vulnerable-target catalog
  models/        SQLAlchemy 2.0 models
  schemas/       Pydantic request/response contracts
  repositories/  all SQL lives here
  services/      business logic, lifecycle orchestration, the Docker adapter
  api/v1/        thin HTTP endpoints
  plugins/       plugin loader

Every non-obvious decision is written down: see the 64 architecture decision records in docs/DECISIONS.md. Full detail in docs/ARCHITECTURE.md.


Local development

# Backend
python -m venv .venv && .venv/bin/activate       # Windows: .venv\Scripts\activate
pip install -e "apps/backend[dev]" -e sdk/python
docker compose up -d postgres redis minio
cd apps/backend && alembic upgrade head
uvicorn app.main:app --reload

# Frontend
npm install
npm run dev                                       # http://localhost:5173
# Tests
cd apps/backend && pytest                         # 874 tests
npm run test --workspace @cyberforge/frontend     # 151 tests

The backend suite runs entirely on in-memory SQLite with fake Docker and storage adapters, so it needs no running services and works offline. The gates are ruff, ruff format, mypy --strict, pytest, tsc, eslint, and vitest.

More in docs/DEVELOPMENT.md.


Documentation

Document Contents
ARCHITECTURE.md Layering, request lifecycle, adapters, degradation
DATABASE.md Schema, relationships, indexes, migrations
API.md Auth flow, error contract, endpoint reference
ENTERPRISE.md MFA, SSO, teams, quotas, roles, webhooks, jobs, scanning, engagements
DEVELOPMENT.md Local setup, workflows, debugging
DEPLOYMENT.md Compose, Kubernetes/Helm, air-gapped, backup, upgrades
PLUGINS.md Writing a plugin, the SDK, the manifest
SECURITY.md Threat model, controls, and what is not protected
TESTING.md Strategy, fixtures, running the suites
STRUCTURE.md Every directory, explained
CODING_STANDARDS.md Conventions and their rationale
CONTRIBUTING.md How to work on this
DECISIONS.md 64 architecture decision records
ROADMAP.md What is next, and what is deliberately not

Security

CyberForge manages Docker. Read docs/SECURITY.md before exposing it to anything but localhost. Three things matter most:

  1. The Docker socket is mounted into the backend. That is equivalent to root on the host. Anyone who can reach the API with sufficient permissions can influence the Docker daemon. A socket-proxy hardened profile narrows, but does not eliminate, that surface.
  2. Image build runs as root on the daemon host. It is off by default, restricted to instructors and above, and audited — but when enabled it is the single most powerful capability the platform offers.
  3. Plugins are trusted code. An enabled plugin runs in the backend process with its full privileges. There is no sandbox, and the documentation does not claim one.

All three are inherent to what the platform does. They are stated plainly rather than buried.


Licence

CyberForge is free software, licensed under the GNU General Public License v3.0 (GPL-3.0-only). See LICENSE for the full text.

You may use, study, share and modify it under those terms; if you distribute a modified version, it must be released under the same licence. Every source file carries an SPDX header to make the licence unambiguous per file.

Copyright (C) 2026 Nithees Narendra S.

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages