Game modes
A game mode tells vulnboxd which game is being played. It then knows what it would otherwise have to be told, and it can read the checker's verdict on each service from the gameserver.
game:
mode: FAUST_2026
team: 123| mode | gameserver | flag format | timing | vulnbox address |
|---|---|---|---|---|
GENERIC | none | from the config | from the config | from the config |
FAUST_2026 | https://2026.faustctf.net/ | FAUST_[A-Za-z0-9/+]{32} | 3 minute ticks from 13:00 UTC on 26 September | fd66:666:<team>::2 |
ECSC_2026 | http://10.60.249.1 | ECSC\{[A-Za-z0-9-_]{32}\} | followed live from the gameserver | from the gameserver |
Anything set in the config itself wins over the mode: a flag, a tick.start, an init.vulnbox_ip. GENERIC is the default and asks nothing of any gameserver.
What a mode does
Fills in the game's facts. The flag format becomes what rules call {flag}. The timing lines the restart window up with the real ticks, and the vulnbox address goes into the capture tools' configuration. A mode that knows which ports its services are on also narrows the capture to them: ECSC_2026 captures 9000-9999, since everything else a vulnbox sends during the ECSC is internal traffic.
Follows the timing. For ECSC, the gameserver publishes when each round starts and ends. vulnboxd reads it with every poll, so its windows stay on the real ticks even if the game is paused or the rounds change length.
Reads the checker's verdicts. Every game.poll (15 seconds), vulnboxd asks the gameserver how the checker found each of your services in the latest tick. It then:
- sends an alert when a service goes down, recovers, or comes back up;
- sets
vulnboxd_sla_upfor each service; - marks the running code and rules as working when the checker says up;
- rolls back a change when the checker says down after it.
| gameserver says | vulnboxd reads it as |
|---|---|
FAUST up, ECSC SUCCESS | up |
FAUST recovering, ECSC RECOVERING | recovering: working, but earlier flags are gone |
FAUST down, faulty, flag not found, timeout; ECSC MUMBLE, OFFLINE, TIMEOUT | down |
FAUST not checked; ECSC CRASHED, REVOKED | nothing: the checker itself failed or the verdict was withdrawn, so it says nothing about the service |
When the gameserver is quiet
On a game network the gameserver will be unreachable sometimes, occasionally for several ticks. When a poll fails, vulnboxd logs it once — not on every poll — and then does nothing at all with verdicts until one arrives:
- No rollback can be triggered. Rolling back happens only on a fresh verdict, so a
downfrom before the gap cannot come back later and undo an innocent change. This is the part worth knowing: silence is safe. vulnboxd_sla_upkeeps its last value. It is not zeroed and does not disappear, so a stale1means "the last thing we heard", not "up now". If you alert on it, alert on the metric's age too.- Nothing is alerted. A gameserver you cannot reach is usually your network, not your service, and it would fire every tick.
gameserver: answering againis logged when it comes back. - The timing stays where it was. A mode that follows the gameserver's rounds keeps the last ones it was told about; if it was never reached at all,
tick.intervalfrom the config is all there is, so set it even underECSC_2026.
Verdicts resume on the next successful poll, and a service that was down throughout is judged then.
Matching services
The gameserver names services as the organisers wrote them, "Interstellar Mission Control" say, and your service directories are called something like interstellar-mission-control. vulnboxd matches them ignoring case, spaces and punctuation. Where that is not enough, say which is which:
game:
mode: FAUST_2026
team: 123
services:
imc: Interstellar Mission Control # directory: name on the scoreboardA service on the scoreboard that matches no directory is logged once, with the setting that fixes it.
Settings
| setting | default | |
|---|---|---|
game.mode | GENERIC | GENERIC, FAUST_2026 or ECSC_2026, in any case |
game.team | your team's number on the gameserver; without it the mode fills in the game's facts but reads no verdicts | |
game.gameserver | the mode's | where the gameserver is, for a game played somewhere else |
game.poll | 15s | how often the verdicts are read |
game.services | {} | service directory to scoreboard name, where they are not alike |
Next year's game
A mode is a preset over the API its gameserver speaks: FAUST's status.json or the ECSC scoreboard API. Next year's game on the same kind of gameserver is a preset of a few lines — a name, its gameserver, flag format, timing, the ports its services are on, and how the vulnbox address follows from the team number — so it is a small pull request rather than new code.
Until it is merged, use the existing mode whose gameserver speaks the same API and point it elsewhere:
game:
mode: FAUST_2026 # for the API, not the year
gameserver: https://2027.faustctf.net/
team: 123
flag: 'FAUST_[A-Za-z0-9/+]{32}' # and set the facts yourselfGENERIC cannot do this: with no preset there is no API to speak, so it reads no verdicts at all.