Skip to content

tbcsec/flagpost

Flagpost

A modern, open-source platform for running Capture the Flag competitions —
self-hosted, real-time, and batteries-included.

License: AGPL v3 CI PRs welcome Backend: FastAPI Frontend: Next.js

Highlights · Features · Quick start · Deploy · Docs


Flagpost is a complete competition platform for CTF organisers: publish challenges, score solves the moment they land, support competitors, and automate the whole event — all from one self-hostable app. It's multi-tenant from the ground up (run many competitions from a single install), real-time throughout (WebSockets, not polling), and ships as a one-command production stack.

Password auth only for now; SSO/LDAP and an AI assistant are on the roadmap but deliberately not built yet.

✨ Highlights

The things that set Flagpost apart — every one of them built and working today:

  • ⚡ Real-time everything. The scoreboard, "who's viewing this challenge" presence, notifications, and support-ticket threads all update live over WebSockets. No refreshing, no polling.
  • 🤖 A visual automation engine. A no-code When → If → Then rule builder: on any event, run actions — notify, call a (SSRF-hardened) webhook, send email, release a hint, unlock a bonus challenge, open a survey, adjust scores, grant an award, freeze the board, or post an announcement. Includes time-based triggers like "an hour before the end, open the feedback survey."
  • 📝 Live collaborative notes. True CRDT (Y.js) co-editing: a shared scratchpad per team on each challenge, and private staff notes on each ticket — everyone types at once, conflict-free.
  • 🛡️ Permissions as data. RBAC that isn't hard-coded: a visual role editor lets you clone the built-ins and craft custom roles with granular, per-competition or site-wide scope.
  • 🧩 A genuinely deep challenge model. Static, regex, and multiple-choice flags; dynamic (decay) scoring; prerequisite unlock chains; scheduled/waved release; tags & difficulty; and per-competition guess caps.
  • 🏆 A scoreboard done right. Live standings with first-blood, parallel brackets/divisions, a freeze for the final stretch, a public spectator board, and a CTFtime feed so rated events just work.
  • 🔁 CTFd-compatible & fully portable. Bulk challenge import/export in the ctfcli YAML format, plus a one-click, full-fidelity platform backup (export/import any section of your install).
  • 🔒 Secure by default. argon2 hashing, a per-install auto-derived JWT secret (no shipped credentials — a first-run setup wizard creates your owner account), SSRF-hardened webhooks, ReDoS-contained regex flags, and timing-safe auth.
  • 🚀 One command to production. docker compose up brings up the whole stack behind a Caddy reverse proxy on a single origin — with automatic HTTPS when you point it at a domain.

📸 Screenshots

Note: these are placeholders. Real captures are on the way — the frames below mark exactly which screens go where (docs/assets/screenshots/).

Live scoreboard
Live scoreboard — real-time standings, first blood, brackets & freeze
Challenges
Challenges — cards, hints, live solves & flag submission
Automation builder
Automation builder — visual When → If → Then rules
Operational dashboard
Operational dashboard — drag-and-drop widgets for organisers

🧩 Features

Competitions

  • Team or individual mode, per competition
  • Public / private visibility & self-serve or invite-code join
  • Schedule, pause, archive, and one-click clone
  • Per-competition module toggles (turn features on/off)

Challenges

  • Categories, static / regex / multiple-choice flags
  • Dynamic (decay) or static scoring
  • Hints, file attachments (S3/MinIO)
  • Prerequisite unlock chains, scheduled release
  • Managed tags & difficulty vocab
  • Bulk ctfcli YAML import/export
  • Manual guess-cap resets for multiple-choice

Scoring & scoreboard

  • Live over WebSocket, first-blood markers
  • Dynamic value convergence (all solvers stay fair)
  • Brackets / divisions, scoreboard freeze
  • Public spectator board + CTFtime feed
  • Manual judge awards & score adjustments

Teams & participants

  • Invite codes, optional captain approval, size caps
  • Team profiles; individual-mode roster with standing

Communicate & collaborate

  • Support tickets with a live staff queue & audio cue
  • Announcements (live banner)
  • Presence — "N others viewing", "a judge is looking"
  • Collaborative CRDT notes on challenges & tickets

Automation, feedback & insight

  • Visual automation rule builder (§ Highlights)
  • Feedback surveys + post-solve challenge ratings
  • Challenge & team analytics
  • Operational dashboard with drag-and-drop widgets

Administration

  • Users directory + soft-ban / lifecycle
  • Data-driven roles & permissions editor
  • Site-wide theming & branding (custom logo, palettes)
  • SMTP, registration policy, cross-competition audit log
  • Full export / import backup (incl. secrets)

🚀 Quick start

Requires Docker with Compose. The default stack is production: built images behind a Caddy reverse proxy, with Postgres, Redis, and MinIO.

git clone https://github.com/tbcsec/flagpost.git
cd flagpost
docker compose up --build

Open http://localhost:8080 and complete the one-time setup wizard to create your owner account. That's it — the app, its API, and its live WebSocket updates are all served same-origin through Caddy, so there's nothing else to configure for a local run.

A fresh install ships with no administrator and no default password. It's unconfigured until you complete the setup wizard, which creates the owner account and initial branding.

🌐 Deploying to production

The default compose is the production stack, so going live is mostly configuration in .env (copy .env.example):

Variable What it does
SITE_ADDRESS Your domain, e.g. ctf.example.com. Caddy obtains & renews TLS automatically. Map ports 80 + 443.
PUBLIC_ORIGIN The browser-facing origin, e.g. https://ctf.example.com. Baked into the frontend at build time — set it before docker compose build.
JWT_SECRET A long random value (required for multi-host; otherwise the app derives and persists one).
POSTGRES_PASSWORD, MINIO_ROOT_USER/PASSWORD Real credentials.
MINIO_PUBLIC_ENDPOINT A browser-reachable MinIO host for signed attachment downloads.

The backend runs as a single process by design (the WebSocket layer is in-process). To run without Docker: build & serve the frontend with npm run build && npm run start, and run the backend with alembic upgrade head then uvicorn main:app (no --reload) behind your own TLS-terminating proxy.

🛠️ Local development

The dev stack mounts source and runs hot-reloading dev servers:

docker compose -f docker-compose.dev.yml up --build
# frontend → http://localhost:3000   ·   backend → http://localhost:8000/docs

Or run each side directly (backend needs Python 3.12 + a venv; frontend needs Node 20+):

# Backend
cd backend && python3 -m venv .venv
.venv/bin/pip install -r requirements-dev.txt
.venv/bin/alembic upgrade head        # against a reachable Postgres
.venv/bin/uvicorn main:app --reload

# Frontend
cd frontend && npm install && npm run dev

Run the checks CI runs before opening a PR:

cd backend  && .venv/bin/pytest        # SQLite-backed, no infra needed
cd frontend && npm run test            # vitest
cd frontend && npx tsc --noEmit && npx eslint .

🧱 Tech stack

Backend — Python · FastAPI · SQLAlchemy 2 (async) · Alembic · PostgreSQL · Redis · MinIO/S3 · JWT + argon2 · a first-class async event bus. Frontend — TypeScript · Next.js 15 (App Router) · React 19 · TanStack Query · Zustand · Tailwind v4 · TipTap + Y.js (CRDT). Realtime — WebSockets throughout. Deploy — Docker Compose + Caddy.

📚 Documentation

Doc What's in it
docs/VISION.md What Flagpost is and why it exists
docs/ARCHITECTURE.md The binding technical design
docs/ROADMAP.md Build order and what's next
docs/adr/ Architecture Decision Records — why things are the way they are

🤝 Contributing

Contributions are welcome! Start with CONTRIBUTING.md for setup, conventions, and the PR flow, and please follow the Code of Conduct.

Found a security issue? Don't open a public issue — see SECURITY.md for private disclosure.

📄 License

Copyright © 2026 Tom Collier.

Flagpost is licensed under the GNU Affero General Public License v3.0. You're free to use, modify, and self-host it; if you run a modified version as a network service, the AGPL's §13 requires you to offer your users its source. The built-in "Powered by Flagpost" footer links every page to this repository, which is how Flagpost surfaces its source to remote users.

Built for the CTF community. Fly your flag. 🚩

About

A modern, open-source platform for running Capture the Flag competitions — self-hosted, real-time, and batteries-included.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Contributors

Languages