HunterFr
CTF platform
A capture-the-flag platform for people getting into security. Challenges across web, reverse engineering, cryptography and forensics, ordered so progress actually goes somewhere instead of throwing beginners at a wall.
- Period
- 2026 →
- Role
- Creator — product, development, infrastructure
- Stack
- Next.js · TypeScript · TailwindCSS · PostgreSQL · Docker · Nginx · Linux
Overview
Most people who quit security quit in the first month. Not because the field is too hard, but because the on-ramp is a wall: a challenge list with no order, a hint system that either tells you nothing or tells you everything, and infrastructure that breaks often enough that you never know whether the bug is in the challenge or in the platform.
HunterFr is my answer to that. It is a CTF platform where the ordering is the product. Each challenge exists to teach one specific thing, and it sits after the challenge that taught you the thing you needed to solve it.
I own all of it — the product decisions, the codebase, the database, and the machines it runs on. That is deliberate: a platform whose whole point is running untrusted code is not something you hand to someone else's abstraction and stop thinking about.
Objectives
- 01
Progression before quantity
A shorter path where every step lands beats a catalogue of five hundred challenges in random order. Adding a challenge means deciding where it sits, not just uploading it.
- 02
The platform is never the puzzle
If a player is debugging the site instead of the challenge, the platform has failed. Uptime and predictable behaviour are features, not operations concerns.
- 03
Assume every player is hostile
It is a security platform: some fraction of traffic is people attacking it on purpose, and that is fine. The isolation has to hold anyway.
- 04
French-first, not French-only
The beginner material that exists in the field is overwhelmingly English. Removing the language barrier is half the on-ramp.
Architecture
| Layer | Choice | Why |
|---|---|---|
| Application | Next.js (App Router) + TypeScript | Server components keep challenge state and flag comparison on the server by construction — the client never receives what it is supposed to be looking for. |
| Data | PostgreSQL | Submissions, scoring and progression are relational and need real constraints. Correct scoring under concurrent submissions is a transaction problem, not an application problem. |
| Challenge runtime | Docker, one container per instance | A vulnerable target is meant to be broken. The blast radius has to end at the container: no outbound network, dropped capabilities, hard CPU and memory limits, short TTL. |
| Edge | Nginx + per-route rate limiting | Flag submission is the one endpoint worth brute-forcing. It gets its own budget, separate from normal browsing. |
Challenges
- Problem
Players share flags. Always. A static flag is public the day the first person solves it.
SolutionFlags are derived per player from a server-side secret and the challenge id. A shared flag is now evidence of sharing rather than a shortcut, and the check stays a single comparison.
- Problem
One container per player per challenge does not fit on one box for very long.
SolutionInstances are reclaimed aggressively: short TTL, idle reaping, and a per-player cap. Most players never notice, because most sessions are shorter than the TTL anyway.
- Problem
Difficulty is not a property of a challenge, it is a property of the gap between the challenge and the player.
SolutionOrdering is authored explicitly rather than derived from solve counts. Solve rate tells me when I got the order wrong; it does not get to decide the order.
Roadmap
- Early 2026Core platform, accounts, scoringShipped
- Early 2026Containerised challenge instancesShipped
- Mid 2026Per-player flag derivationShipped
- Late 2026HuntCode editor integrationIn progress
- 2026Team mode and private eventsPlanned
- 2026+Community challenge submissionsPlanned
What it taught me
Writing a challenge is easy. Deciding where it goes is the work, and it is the part nobody sees.
Running the thing I attack all day changed what I look for when I test someone else's application — I now read for the operational assumptions first.
Isolation you have not tried to escape yourself is a claim, not a control.
HuntCode
