Skip to content

What is vulnboxd? ​

vulnboxd runs on your vulnbox during an attack-defense CTF and takes care of the services on it: it sets the box up for your team, deploys your patches, filters the traffic that reaches the services, and watches them, rolling a change back when it breaks one. It is purely defensive.

It comes as two programs — vulnboxd, the daemon systemd runs, and vulnboxctl, for everything a team does by hand — plus vulnboxd-pcap, the capture broker, if you turn it on.

Setting up the box ​

At the start of a game, vulnboxctl setup-interactive (or vulnboxctl init from a script) prepares everything in one go, for all services in parallel:

  • A git repository per service, with the current state committed, large files kept out, and the branch named main.
  • A private GitHub repository per service, each with its own deploy key, pushed and set up so git push works from the service directory, by hand as well.
  • Configuration for the capture tools: tulip.json and shovel.yaml, listing every published service port.
  • vulnboxd itself: the binaries, its config, a systemd unit and a guard in front of docker compose, started.

It is safe to run again. A service that failed can be fixed and retried without touching the others, and an optional webhook reports when a run starts and finishes.

During the game ​

It knows the game. A game mode for FAUST CTF 2026 or the ECSC 2026 brings the flag format, the tick timing and the vulnbox address, and reads the checker's verdict on each service from the gameserver.

It finds your services. Every directory under /srv (configurable) with a compose file is a service, the way FAUST CTF lays out its vulnboxes. vulnboxd reads each compose file, after .env and environment substitution, to learn which ports are public, and picks up new services as they appear.

It deploys your patches. vulnboxd polls each service's git repository. As soon as one changes, it builds and pulls the new images while the old version keeps serving, and swaps the containers in the last few seconds of the tick (a window of 5 seconds by default). While a container restarts, the proxy holds new connections instead of refusing them, so a deploy that runs over shows up as a slow request rather than a failed check.

It filters their traffic. Every public port is moved behind a proxy inside vulnboxd, which applies the rules in the service's vulnboxd-rules.yml:

  • Block, rewrite, stall or log tcp, udp, http (including h2c and WebSockets), ftp and dns traffic, matched as it streams by, at any distance, in constant memory.
  • Edited through git and reloaded within a second, with a bypass header so your own exploits still get through. Rules covers the rest: flag egress, routing, mirroring and the cookbook.
Where vulnboxd sits Traffic from the game network reaches the public port, which vulnboxd now holds. It applies the incoming rules, forwards to the container on a loopback port, and applies the outgoing rules to the answer. Game networkcheckerattackersvulnboxd:8080public portrulesin: blockrewrite, routeout: blockrewrite, redactContainer127.0.0.1:20000unchanged compose+ generated override
The public port moves into vulnboxd; the container keeps running as before, on loopback.

It rolls back what breaks. A new version that does not come up, or a change after which the checker says down, is undone with a commit that is pushed, and deployed. vulnboxctl rollback does the same by hand.

It keeps the rules with the team. Each service's rules file is created for it, committed and pushed, so everyone edits rules through git. Saving the file reloads the rules within a second, without a rebuild or a dropped connection; a broken file is reported and the previous rules stay.

It tells you what is happening. Alerts go to a Discord webhook when a port stops answering, the checker says down, a build or deploy fails, or something is rolled back. vulnboxctl top is a terminal dashboard of services, ports, connections and which rules are firing, and the same numbers are served as Prometheus metrics. A rule set to action: log shows what it would block before you let it.

What it does not do ​

It is worth knowing the edges before you put it on a box ten minutes before a game:

  • No attacking. No exploits, no flag submission, nothing to help with your own attack tooling. It only ever stands between other teams and your services.
  • No host hardening. It does not firewall the box, manage users or patch your packages. It filters the ports your services publish, and nothing else.
  • GitHub-shaped setup. init creates repositories and deploy keys on GitHub. Any other remote works, but you point origin at it yourself.
  • One box. It manages the services on the machine it runs on; nothing is shared between vulnboxes.
  • x86_64 releases. Built for glibc on x86_64; anything else is a cargo build --release away.

Next steps ​