╔══════════════════════════════════════════════════════════════╗
║ ║
║ ██████╗████████╗███████╗ ██████╗ ██████╗ █████╗ ██████╗║
║ ██╔════╝╚══██╔══╝██╔════╝ ██╔══██╗██╔══██╗██╔══██╗██╔════╝║
║ ██║ ██║ █████╗ ██████╔╝██████╔╝███████║██║ ║
║ ██║ ██║ ██╔══╝ ██╔══██╗██╔══██╗██╔══██║██║ ║
║ ╚██████╗ ██║ ██║ ██████╔╝██║ ██║██║ ██║╚██████╗║
║ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝║
║ ║
║ 1 v 1 T O U R N A M E N T ║
║ ║
╚══════════════════════════════════════════════════════════════╝
Real-time 1v1 CTF tournament bracket platform with a cyberpunk UI.
Players register, get matched in a single-elimination bracket, and race head-to-head to capture flags. First correct submission wins.
Built for live on-stage cybersecurity competitions.
| Homepage | Live Bracket |
|---|---|
![]() |
![]() |
Tip: The
/bracketpage is designed for projector/stream display during live events.
- Single-elimination bracket with play-in rounds (3-16 players, zero byes)
- Random seeding, random per-match challenge assignment (no repeats)
- Auto-advance winners through the bracket
- 10-minute match timer with overtime alerts + random tiebreaker
- Real-time bracket view with 3-second polling
- Player dashboard with VS display, challenge description, flag submission
- Victory / defeat / champion overlays with animations
- Sound effects (match start, correct, wrong, win, defeat, champion) with mute toggle
- Manage players, challenges, bracket, and matches from one page
- Start matches on demand (auto-spins up Docker challenge containers)
- Manual winner override for overtime situations
- Full tournament reset
- 4 color themes: Cyberpunk (gold/cyan), Hacker (matrix green), Synthwave (pink/purple), Blood (red/orange)
- Configurable event name, player cap, and secrets via environment variables
- Glitch text effects, neon glow buttons, HUD-style terminal cards
- Animated matrix rain background on landing page
git clone https://github.com/mazala-cybersec/mazala-ctf-bracket.git
cd mazala-ctf-bracket
make setup # install deps, copy .env, init database
make dev # start dev serverManual setup (without Make)
npm install
cp .env.example .env # edit with your secrets
npx prisma db push
npm run dev- Open
http://localhost:3000/admin - Login with the
ADMIN_SECRETfrom your.env - Add challenges (title, description, flag, category, points)
- Have players register at
http://localhost:3000/register - Generate the bracket, start matches, and watch the tournament unfold
| Variable | Description | Default |
|---|---|---|
DATABASE_URL |
SQLite database path | file:./dev.db |
JWT_SECRET |
Secret key for signing JWT tokens | (required) |
ADMIN_SECRET |
Password for admin login | (required) |
MAX_PLAYERS |
Maximum player registrations allowed | 16 |
CTF_ROOT |
Directory containing your docker-compose.yml |
cwd |
NEXT_PUBLIC_EVENT_NAME |
Event name displayed in UI | CTF BRACKET |
NEXT_PUBLIC_EVENT_HIGHLIGHT |
Word highlighted with glitch effect | CTF |
Click the colored dot in the top-right corner of any page to switch themes. Preference is saved per browser.
| Theme | Colors | Vibe |
|---|---|---|
| Cyberpunk | Gold + Cyan | Default, classic CTF |
| Hacker | Green on black | Matrix / terminal |
| Synthwave | Pink + Purple | Retro neon |
| Blood | Red + Orange | Dark and aggressive |
REGISTRATION BRACKET MATCH LOOP CHAMPION
──────────── ────────── ───────────── ──────────
For each match:
Players Admin 1. Admin clicks
register ---> generates ---> "Start" ---> Tournament
via web bracket 2. Challenge spins complete!
(random seed) up in Docker
3. Players race
to submit flag
4. First correct = win
5. Winner auto-advances
| Route | Description |
|---|---|
/ |
Landing page — animated background, status, registration link |
/register |
Player registration (handle + password) |
/login |
Player login |
/player |
Player dashboard — VS display, challenge info, flag submission |
/admin |
Admin dashboard — players, challenges, bracket, match controls |
/bracket |
Live bracket view — ideal for projector or stream overlay |
When admin starts a match, the platform runs docker compose up -d <service> to spin up the challenge container automatically.
1. Define your challenges in docker-compose.yml:
services:
my-web-challenge:
build: ./challenges/web
ports:
- "4001:5000"
environment:
- FLAG=flag{my_secret_flag}2. Map titles to service names in src/lib/challenge-runner.ts:
const CHALLENGE_SERVICES: Record<string, string> = {
'Web Injection': 'my-web-challenge',
'Binary Bomb': 'my-reversing-challenge',
};3. Add the challenge via admin dashboard or API.
See examples/ for a complete working setup.
Go to /admin > Challenges tab > fill in the form.
# Login as admin
curl -c cookies.txt -X POST http://localhost:3000/api/admin/login \
-H 'Content-Type: application/json' \
-d '{"password": "your-admin-secret"}'
# Add a challenge
curl -b cookies.txt -X POST http://localhost:3000/api/admin/challenges \
-H 'Content-Type: application/json' \
-d '{
"title": "SQL Injection 101",
"description": "The search bar looks vulnerable...\n\nTarget: http://localhost:4001",
"flag": "flag{sqli_master}",
"category": "web",
"points": 100
}'# Edit examples/seed-challenges.py with your challenges, then:
python3 examples/seed-challenges.py [HOST]| Command | Description |
|---|---|
make setup |
Install deps, copy .env, initialize database |
make dev |
Start development server |
make build |
Production build |
make reset |
Wipe database and recreate schema |
make seed |
Seed example challenges (server must be running) |
make clean |
Remove all build artifacts and dependencies |
mazala-ctf-bracket/
src/
app/
api/
admin/ Admin API (login, players, challenges, bracket, matches)
auth/ Player auth (register, login, logout, session)
bracket/ Public bracket data endpoint
player/ Player match info + flag submission
admin/ Admin dashboard page
bracket/ Live bracket view page
player/ Player dashboard page
login/ Login page
register/ Registration page
components/
bracket/ BracketView, ChampionDisplay, ConnectorLines,
MatchCard, PlayerSlot, RoundColumn
ui/ GlitchText, NeonButton, TerminalCard, CyberGrid,
ThemeSwitcher, SoundToggle, ScanlineOverlay
lib/
auth.ts JWT + bcrypt authentication
bracket.ts Tournament generation + bracket logic
challenge-runner.ts Docker container lifecycle management
config.ts Environment-based configuration
sfx.ts Sound effects via Web Audio API
player-colors.ts Neon color palette for player avatars
ratelimit.ts In-memory rate limiter
prisma/
schema.prisma Database schema (Player, Challenge, Tournament, Match, Submission)
examples/
docker-compose.yml Example challenge container setup
seed-challenges.py Example challenge seeding script
| Layer | Technology |
|---|---|
| Framework | Next.js 16, React 19 |
| Language | TypeScript 5 |
| Styling | Tailwind CSS 4 with CSS custom properties |
| Database | SQLite via Prisma 6 |
| Auth | JWT (jose) + bcryptjs |
| Audio | Web Audio API |
| Containers | Docker Compose |
- Race-condition-safe flag submission (Prisma transactions + optimistic locking)
- Input validation and type checking on all API endpoints
- Rate limiting on auth and submission endpoints
- Flags masked in admin API responses
- Player handle sanitization (alphanumeric + underscore/hyphen only)
- httpOnly, sameSite cookies for JWT tokens
- No raw SQL — all queries via Prisma ORM
Contributions are welcome! Feel free to open issues or submit pull requests.
- Fork the repository
- Create your feature branch (
git checkout -b feature/something) - Commit your changes
- Push to the branch
- Open a Pull Request
MIT - Built by Mazala Cybersec

