Skip to content

Watching with metrics ​

vulnboxd serves Prometheus metrics on http://127.0.0.1:9330/metrics. This is also what vulnboxctl top reads.

Keep it on localhost

The metrics say which of your rules are firing, which is exactly what an attacker would like to know. Leave metrics bound to 127.0.0.1, and reach it over your team's own network or an ssh tunnel.

During a game, the quickest look is:

sh
curl -s localhost:9330/metrics | grep rule_hits

The one to watch ​

vulnboxd_rule_hits_total counts every time a rule fires, labelled with the service, port, rule name and what it did: blocked, stalled, rewritten, or matched for a rule set to action: log. That last one lets you see what a rule would have blocked before you let it.

text
vulnboxd_rule_hits_total{action="blocked",port="8080",rule="scanners",service="web"} 1432
vulnboxd_rule_hits_total{action="matched",port="8080",rule="watch logins",service="web"} 87

Everything else ​

See the metrics reference for the full list. The ones worth a panel:

  • vulnboxd_sla_up is whether the checker found each service working in the latest tick, which is the one number that says whether you are scoring. With no game mode there is nothing to ask, so it is absent rather than zero.
  • vulnboxd_flags_out_last_tick is how many flags left the box in the last tick. Your checker's reads make a steady baseline; a rise above it is someone's exploit working.
  • vulnboxd_http_responses_total{class="5xx"} climbing means a service is unwell, or a rule is breaking requests.
  • vulnboxd_upstream_errors_total means the proxy could not reach the service at all.
  • vulnboxd_deploy_ms is how long the last deploy took; it should stay well under the window.
  • vulnboxd_builds_total{result="failed"} and vulnboxd_git_polls_total{result="failed"} mean a new version is not going to arrive.
  • vulnboxd_bypassed_total climbing while nobody on your team is testing means your bypass value has leaked.
  • vulnboxd_mirror_total shows whether a mirrored rewrite agrees with the real service.

Grafana ​

contrib/grafana/vulnboxd.json is a ready-made dashboard, also in every release:

  • Overview: services, open connections, rule hits, and how many ports and services are down.
  • Health: which ports answered their checks, and the checker's verdicts, over time.
  • Rules: hits per rule and action, the busiest rules, bypassed requests, mirror comparisons.
  • Flags: flags that went out in each tick, and the total per service and port over the time range.
  • Traffic: connections, throughput, HTTP responses by class, and requests the service never got.
  • Deploys: how long the last deploy took against the window, deploys, builds and rollbacks.

Deploys and rollbacks are also marked on every graph. A filter at the top narrows it to some services.

To use it, point a Prometheus at vulnboxd. The metrics stay on localhost, so run Prometheus on the box itself, or reach them through an ssh tunnel:

yaml
scrape_configs:
  - job_name: vulnboxd
    scrape_interval: 5s
    static_configs:
      - targets: ["127.0.0.1:9330"]

Then, in Grafana, go to Dashboards → New → Import, upload vulnboxd.json and pick that Prometheus.

To change the address, or turn metrics off:

yaml
metrics: 127.0.0.1:9330   # or null to disable

The status world ​

A toy, off by default, and honest about being one: a Minecraft server where each service is a pillar with a lamp on top, lit while it is working.

It ships in the release archive as vulnboxd-mc, built separately from the daemon so that the binary on the box during a game carries none of its three hundred dependencies. The setup asks whether you want it, and nothing happens unless you say yes:

sh
sudo vulnboxctl setup-interactive   # "status world", with its address and password

or, without the terminal interface:

sh
sudo vulnboxctl install --minecraft --minecraft-listen 127.0.0.1:25565

which writes the section below, installs vulnboxd-mc.service and starts it. In the config:

yaml
minecraft:
  enabled: true
  listen: 127.0.0.1:25565   # keep this off the public internet
  password: ''              # empty leaves the gated plot shut to everyone

Turning it off again stops it: the setup disables the service and takes its unit away, rather than leaving a port open that somebody once said yes to. An upgrade replaces the binary and restarts the world, which gives whoever is connected the countdown described below.

Building it yourself is cargo build --release --features minecraft; the daemon's own build does not include it.

It reads the same metrics endpoint vulnboxctl top does, so it needs metrics left on, and it can reach nothing else on the box.

Getting your bearings ​

You arrive in a stone plaza, facing a wall of three signs that says what is where. From there:

directionwhat is there
north-easta pond, which is water to build with as much as to look at.
norththe instruments: a pillar per service with its own sign, a line of lamps carrying the requests that arrive at it, and a post with the traffic totals. A boardwalk leads to them.
eastthe scoreboard screen.
westthe start of the fixed parkour course; it finishes back in the plaza.
southgrass, as far as the world goes. That is the build area.

Everything north of the stone line is the world's own and cannot be changed, and so is the plaza itself — floor, lanterns, plaque and directory. A plaza whose floor can be mined out from under the instructions is one where nobody can read what the place is. Everything south of it is yours, except the scoreboard screen and the records wall, which are furniture.

Two worlds ​

What the rules are doing is the one thing another team must not read off a wall: rule names and hit counts tell them exactly what you filter and whether their exploit got through. So the world is split, and not by a fence — a client is sent every block of a chunk it can see, so a walled room would still be readable. The board is a separate chunk layer that the public never has, which is the only way to keep it off the wire.

The board is a superset of the yard: someone with the password sees everything, including what people have built out there and where they are standing. The yard sees only itself. Every player's body lives in one shared entity layer — entity visibility is a set where chunk visibility is a single layer — so everyone can see everyone wherever they are, while the board's blocks are never sent to a client that has not asked for them.

Chat is shared by everyone, in both worlds. The way in is a command, not a chat message, for exactly that reason — a password typed as ordinary chat would be handed to everyone in the world, including whoever it is being kept from:

command
/password <word>takes you to the board, if it is the right word
/yardtakes you back
/rules <service>that service's rules file, in chat. Only answered to someone on the board
/endlessstarts an endless parkour run in a lane of your own; /yard ends it

A visitor to the yard is not told that any of this exists. Nothing there mentions rules, a board or a password, and /rules is ignored in silence rather than refused, because a refusal would itself say there is something to refuse. Someone with the password knows it from you, not from the server.

The black box, out past the instruments, is where the board lives — its own place rather than a layer laid over the yard:

  • From the public world it is a windowless box of black concrete with an iron door that does not open, at the end of a lit path, with two chests of censored flags at its foot. That says something is kept in here and nothing whatever about rules, passwords, or what the box filters. An iron door needs redstone in the game, so the door that does not open needs no special rule to refuse: it simply is a door that does not open.
  • Its inside is solid rock in the public world rather than an empty room. Nobody can break in and nobody can open the door, so an empty room would do for honest players — it is the other kind this is for. A client that can see through blocks finds rock where the board is.
  • Saying the password moves you to the other layer, standing on the path outside the door, and there the door swings. Inside is every instrument that would give the game away: the traffic wall on the far side, a station per service down one wall with its response-class bars and the air above them where its smoke, its zombie and its mirror flash appear, the glass pit of held connections, the bypass beacon under a skylight, and the tick clock's ring.
  • A door seen to open is a door somebody knows the word for, so the swing is written to the board's layer only, and is deliberately not remembered: what the yard remembers is put back into both worlds at the next start.
  • Outside the box the two worlds are identical, block for block, and a test walks the whole of the instruments and the north of the yard asserting it.

Chat is shared, so people can leak what the server will not

The gate decides what the server sends. It cannot stop someone on the board typing what they see into a chat window the yard can read, so the board says so when you arrive.

The yard, where everyone arrives:

  • A hundred and sixty blocks by a hundred and twenty-eight of grass — about four hundred blocks of ground each for fifty people, which is enough for fifty separate ideas. Arrivals are scattered around the plaza rather than stacked on one block, and --max-players defaults to 64.
  • Grass, Survival mode, build what you like. What you build is kept between restarts, and everyone arrives with a kit in their hotbar: stone tools, a hoe, a bucket, a few loaves, and a stack each of planks, stone bricks, concrete, glass and torches. A block you break drops where it stood and goes into your pack when you walk over it; a block you place comes out of your pack. Under the dirt is bedrock, which nothing may break, so digging down cannot drop you out of the world.
  • A workshop in the plaza: four crafting tables on one side, four furnaces on the other, on the one piece of ground nobody can mine out from under them. Both are craftable too — four planks and eight cobblestone — so nobody has to walk back.
  • A farm, between the plaza and the pond: four nine-by-nine plots of wet farmland with a channel of water down the middle of each, planted with wheat, carrots, potatoes and beetroot at every stage of growth. Breaking grass anywhere gives seeds, a hoe turns the yard's own soil over, and three ears of wheat are a loaf of bread. This is what the hunger bar is for.
  • A hill with ore in it, out in the south-west, with a lit tunnel driven into the side of it. Coal and copper near the surface, iron through the middle, gold, redstone and lapis lower down, and diamond and emerald in the deepslate at the bottom. Down there too, where the game would never put them, are a little quartz and glowstone: a comparator needs quartz and a lamp needs glowstone, and there is no nether here — so the world bends rather than the recipes. A pickaxe too poor for a seam takes the block and gives you nothing, as in the game. It grows back: a few new veins every couple of minutes, on faces somebody has already dug open, so fifty people with pickaxes cannot strip it for good.
  • Two doorways out, east of the plaza: a lit nether portal and an end portal, both built finished — lighting one wants flint and steel, the other twelve eyes of ender, and the point is the doorway rather than the ritual. Stand in the obsidian frame for two seconds and you are in a hall of netherrack; step into the end portal and you are on an island of end stone in the void. Each is empty, says so on a sign, and has a portal home standing beside where you arrive. They are layers of their own, far outside the yard's coordinates, so nothing there can be built or broken — and everything that draws the yard is marked to write to the two mirrored worlds only, which is what stops a bucket emptied in the yard from pouring itself into the nether.
  • A hedge maze, west of the yard: fifteen cells square, carved by the oldest algorithm there is, with a lamp in the middle and a way out at the far corner. It comes back the same after a restart, because the way through is the only thing a maze has.
  • A high board over the pond, twenty-two blocks up a ladder. Water breaks a fall completely, as in the game, which is what makes the board worth climbing and a pool worth digging under a tower.
  • A ring, out east: a sunken square of sand inside a wall, with a gate at each side. Players can hit each other, so this is where that goes.
  • A line of redstone lamps per service, running from the yard's edge to that service's pillar, with a pulse travelling up it for each connection that arrives. A quiet service is a dark line; a busy one is a stream. It says how much is arriving and nothing else — no history to read a pattern off, and nothing about what was stopped, which is the part that would tell another team how well the rules work.
  • Signs with the numbers on them: one on each service's pillar facing the plaza — its name, whether it is answering, how many connections are arriving per second and how many there have been altogether — and one post where the plaza meets the instruments with the bytes in and out per second and the connection rate across everything. The lamps say that requests are arriving; the signs say how many.
  • Parkour, west of the yard, in two kinds. The fixed course's own blocks, and the space a runner lands in above each of them, cannot be broken or built on: a course with a hole in the middle is a course nobody can finish, and paving it with a ramp makes the times on the wall beside it meaningless just as thoroughly. /endless is the one to build and break in, where every runner has a lane to themselves. A fixed course over a pit, so missing a jump costs a fall and a climb back up the steps at the near end rather than a stroll; it says Go when you step on the green block, counts up in the action bar while you run, and times you to the gold one, with best times per player in the sidebar; and /endless, which drops you onto the first block of your own corridor of sky, facing the first jump, and generates blocks ahead of you until you fall off. Each runner gets a separate lane, so you can see each other without landing on each other's blocks. Both are jump-tested: nothing in either exceeds four blocks flat or one block up. Both exist in both worlds, and lanes are handed out once for everyone, so two people racing from opposite sides of the gate never land in the same corridor.
  • How many services are up, in the player list and above your head. That is observable by anyone attacking them. When the deploy window falls is not shown here: it would say when the services blip and when traffic stops being let through.
  • Two chests of captured flags, where someone hunting for the board would look. They are built the way the game builds one — an ESCS{…} wrapper around a base64 payload of round, team, service and flagstore ids plus sixteen bytes where the signature would be, which is 24 bytes and so exactly 32 base64 characters — and each says underneath it which round and team it claims to come from. The sixteen bytes are mixed from the ids rather than signed: nothing in the world can check a signature, so an HMAC would only be a slower way of filling the same space.
  • Rain when a service is down — which anyone attacking it can see anyway.

The board, behind /password (set with --password):

  • A pillar per service with a lamp on top. Green and lit means up, red means down, gold means nothing has been said about it yet. Without a game mode there are no checker verdicts, so the port checks decide.
  • The same traffic wall, with the part a rule stopped stacked on top in red. A bar taller than the wall is capped with magenta rather than quietly clipped. A block and a stall both count as stopped.
  • A sidebar of connections, what has been stopped, and what is open now.
  • A rules board under each player's name, ranking rules by how often they have fired. The quiet ones matter as much: a rule that has never fired is usually a rule that does not work.
  • Smoke, a note and a briefly burning zombie at a service's pillar whenever a rule stops something.
  • A glass pit holding one zombie per connection a stall rule is keeping. Everything else on the board follows a counter; this follows a gauge, so the population is the number — it fills as the tarpit catches and empties as it lets go.
  • Three glass bars at each pillar: how the service's own answers divide between 2xx, 4xx and 5xx. A request a rule stopped never reaches the service and is not counted here — that is the red part of the wall — so these separate "the service turned it down" from "we turned it down". Climbing red means a service that is unwell, or a rule mangling requests rather than stopping them.
  • A beacon that lights while the bypass header is being used. Climbing while nobody on your team is testing means the value has got out, which is worth a beam into the sky.
  • A flash and a chime when a mirrored answer stops agreeing with the real one, which is the only thing a mirror is set up to find.
  • A ring that fills as the tick runs out and goes gold while the deploy window is open.
  • Thunder on a rollback or a failed build, and an announcement in the action bar.
  • Saying a service's name returns its rules file in chat.
  • The countdown to the next deploy window in the sidebar, with what is held by a stall, what the mirrors disagreed on, and how often the bypass has been used.
  • Creative mode, because the password has already been paid and flying up to read the wall is half the reason to come here. Digging the board is simply refused and put back; the yard is the only part anyone may change. Not Spectator, tempting as that is for the flying: a spectator is invisible to everyone else, and being able to see each other across the gate is the point.
  • The whole yard, as it really is — everything outside the gate is mirrored between the two. Blocks broken and placed, the plaza, the parkour, the chests, the lamps and the signs are written to both worlds, so the two yards stay the same yard and the board is only the yard plus what the password buys.

The yard is the only part anyone may change, wherever they are standing; the board is redrawn every couple of seconds, so nothing done to it would last anyway.

A bar above your head counts how many services are up, and so does the player list. The multiplayer list shows the count without joining at all. None of the three mentions the deploy window: the countdown is on the board, because when the window falls says when the services blip.

The game's scoreboard ​

The game publishes its scoreboard to everyone, so it is shown in the open part of the world, east of the plaza: a wall of cards, one per team, with rank, name, points and the attack/defence/SLA split, best first. It follows the game you picked — the mode's own gameserver is where it reads from — and minecraft.scoreboard overrides that, with none to leave the screen blank:

yaml
minecraft:
  scoreboard: https://scoreboard.example.ctf

It speaks the ECSC 2026 player API, reading three endpoints under that base URL:

endpointfor
/api/scoreboard_current.jsonwhich round has been scored, and whether the game is running or frozen
/api/scoreboard_round_<n>.jsonthat round's table
/api/scoreboard_teams.jsonteam ids to names

Every field is optional on the way in. The scoreboard is somebody else's service: a field that moves or an endpoint that goes away costs a line on a wall, not the world. It is read twice a minute, and the screen keeps showing the last round it managed to read.

The round shown is the one the game says has been scored, not the one being played, which is why it can sit a round behind the website during scoring.

Survival, as far as it goes ​

valence is a library for writing a server, not a server: it simulates no damage, no hunger, no physics and no item on the floor. What the yard has, it has because this implements it — and what it does not have, it does not have:

workshow
hungereffort fills up exhaustion, exhaustion eats saturation, saturation gone takes the food bar down, and an empty bar starves you a heart at a time. Sprinting and mining are the two things that cost.
eatinghold right-click for 1.6 seconds; the food leaves your hand and the bar fills. A full bar refuses a meal.
deathby falling far enough, falling out of the world, or starving. Respawning puts you back on the side of the password you were on, with a fresh body.
dropped itemsthrowing one lands it in front of you, where it falls to the ground and waits. Walking over it picks it up. Five minutes and it gives up.
fall damagepast three blocks, a heart a block, with the flash and the thud. It is worked out the way a real server works it out: the client says where it is and whether it is standing on something. A move the server makes — between the worlds, out of a run in the sky, a respawn — is not a fall, however far it is.
mininga broken block drops what the game would drop — grass leaves dirt, stone leaves cobble, a tuft of grass leaves seeds, iron ore leaves raw iron — and a flower standing on it comes off with it. Blocks that come apart instantly, like grass and torches, break on the first click, because that is the only thing the client reports for them.
tool tiersa block gives nothing up to a tool that is too poor for it: coal to anything, iron and lapis to stone or better, gold, redstone, diamond and emerald to iron or better, and nothing at all to the wrong kind of tool. How long a block takes is the client's own sum, as in any game; what it drops is the server's, and this is that rule.
craftinga crafting table opens a real three-by-three grid, and the two-by-two in your own inventory works as well. Shaped and shapeless recipes, ingredient groups (any planks, any cobblestone, coal or charcoal), patterns matched wherever in the grid they are put, a result slot that previews what the grid makes, and the ingredients spent when the result is taken. The recipes are hand written: wood, stone, the five tools in five materials, a furnace, a chest, doors, fences, stairs, slabs, ladders, buckets, shears, flint and steel, torches, panes and bread. The game has about a thousand; this has the ones the yard is for. A shaped recipe made with a group gives the oak version of the thing.
TNTlit by flint and steel or by power, a four-second fuse on a charge that falls and drifts, and then the game's own blast algorithm rather than a sphere: sixteen by sixteen rays fired out of the cube around it, each with a random share of the strength, each spent as it travels by a fixed amount per step plus whatever the block it passes resists. That is why a crater runs further through dirt than stone and why it is ragged. A quarter of the rubble is dropped, damage and knockback fall off with distance with Creative immune, and a charge caught in a blast is lit rather than broken, so a pile of it chains. What the yard will not let a pickaxe touch — the plaza, the course, the box, the world's floor — a charge cannot touch either.
redstonewire carrying 0–15 and losing one a block, with the game's own shapes: it climbs the side of a solid block, runs down past one that is not, and a line with nothing on the other axis carries straight through — which is how a run ending at a lamp comes to be pointing at it. Strong and weak power are different things, as in the game: a switch fixed to a block, a repeater or comparator facing it, or a torch under it powers that block strongly, and only strong power feeds wire; wire lying on a block or pointing into it powers it weakly, which works mechanisms but cannot feed more wire. Sources: redstone blocks, torches, levers, buttons (stone a second, wood a second and a half), pressure plates. Mechanisms: lamps, note blocks, doors, trapdoors and fence gates — including iron ones, which is the whole point of an iron door. Diodes: repeaters with their four delays and side-locking, comparators comparing and subtracting and reading how full a furnace is, observers pulsing at a block change. Pistons shove up to twelve blocks and refuse the thirteenth, break what cannot travel, and a sticky one pulls its block back. Every delay is scheduled in redstone ticks, so a torch fed back to its own base is a clock.
the recipe bookthe client's own book, filled in: every recipe here is sent to it in the game's wire format and marked known, so it can be browsed and searched rather than guessed at, and clicking one lays the ingredients out in the grid from your pack. valence carries the packet but not its contents — and cannot send the unlock packet at all, having only a decoder for it — so both are written out here.
smeltinga furnace belongs to its block, not to whoever opened it, so two people can watch the same fire and the ore is still there when you come back. Fuel burns for the game's own number of ticks — sixteen thousand for a block of coal, sixteen hundred for a piece, three hundred for planks — ten seconds to the item, progress sliding back when the fire goes out, and the block lit while it burns so it can be seen from outside. Raw iron, copper and gold become ingots; cobblestone becomes stone; sand becomes glass; logs become charcoal; a potato bakes. Mining a furnace out drops what was inside it.
farminga hoe makes farmland, farmland within four blocks of water is wet farmland, a seed goes in on top, and crops climb through their eight stages — about half as fast on dry ground. Wheat, carrots, potatoes and beetroot, which counts to three rather than seven. Pulled up early a crop gives the seed back and nothing more; ripe it pays for itself and the next planting. A crop whose farmland is dug out from under it pops off, as in the game.
flyingcaught, in the yard. The server believes whatever position a client sends — valence has no physics to check it against — so what is watched for is the one thing flight cannot hide: staying up with nothing underneath for longer than any jump lasts. Whoever does gets put back where they last honestly stood, every tick until they stop. A move the world makes is exempt, and so is Creative on the board, where flying is the point.
moving too fastcaught, with a budget rather than a limit. Each tick adds a little distance to what a body is owed, up to a few seconds' worth, and moving spends it: a connection that stalls and then arrives where it would have got to is paid for out of the savings, while a client that simply moves quicker than a body can runs out and is put back. Support is checked against the world under all four corners of the body, not taken from the client's own word for it.
placingonly into room: never over a block that is already there, and never inside a player. Clicking a tuft of grass puts the block in its place, and clicking the empty half of a matching slab fills that block in — the game's three rules for where a block goes, all of which the client predicts.
directiona block goes down the way it was put down, by the family it belongs to: a sign or torch on the side of a block becomes its wall form facing out of that side; a log takes its axis from the face you clicked; a piston or observer points back along your line of sight, up and down included; a door, stairs, a gate or a bed faces the way you face while a furnace or chest faces back at you; a standing sign takes one of sixteen rotations.
two-space blocksa door, a bed and a tall flower take both of their spaces, and both halves come up together when either is broken. One with no room for its other half is refused rather than left as half a door.
gravitysand, gravel, concrete powder and anvils fall when what holds them up goes. The block becomes a falling-block entity, so it is drawn tumbling the way the game draws it, and turns back into a block on whatever stops it.
joining upwalls, fences, panes and bars connect to what is beside them, and a wall drops its post where the run is straight — recomputed for a block and its four neighbours on every change.
double slabstwo halves of the same slab make one whole block.
fluidswater and lava flow, by the game's own rules: scheduled ticks (water moves every five, lava every thirty), eight levels of fullness with falling water staying full, spread towards a hole rather than evenly, two sources making a third so a pool is endless, and the two meeting to make obsidian, cobblestone or stone. Water washes away anything you can walk through, as it does in the game, and stays inside the yard — a flood may ruin somebody's tower but not the instruments.
bucketsfill from any source, empty to place one. There is a pond in the north-east of the yard to fill from, and a bucket in the kit.
doorsright-click a door, gate or trapdoor to swing it; both halves of a door move together. Iron needs redstone, so iron doors stay shut.
does notwhy
drowning, mobsthere is nothing to fight and nothing that drowns; players hit each other, and that is all the combat there is.
a target rangethere is nothing to shoot with: a bow needs string and arrows need feathers, and both come off mobs. The ring is what the lobby has instead.
the nether and the end, as placesno fortresses, no fog of their own beyond the dimension's, no mobs, nothing to gather. They are two rooms with a sign in each, which is what was asked for.
fireand so TNT cannot be lit by fire spreading to it, only by hand or by power. There is nothing here for fire to do except destroy what people have built.
gunpowderit comes off a creeper and there are no mobs at all, so TNT is made with coal in its place. The one recipe that bends rather than the world.
blast shieldingthe game works out how much of a body an explosion can actually see; this takes the plain distance, which is the same answer in the open and a kinder one behind a wall.
the exotic half of redstonequasi-connectivity and the block-update detectors built on it, the exact order two components in one tick update in, zero-tick contraptions, and torch burnout. Circuits that depend on those are depending on bugs the game kept; everything built on levels and delays works.
redstone that needs other systemsrails and minecarts, hoppers and item transport, dispensers and droppers, tripwire, daylight sensors, target blocks. Each would be a subsystem of its own before it was a redstone component.
sticky pistons, in practicethe code moves them, but slime is not obtainable here, so one can only be had in Creative.
the slidepushed blocks arrive rather than slide: the game draws the slide with a block-moving entity this world has no model for. The piston head is animated, which is the part that reads as movement.
tool durabilitynothing here wears out. It would mean tracking damage in item NBT and a bar to draw it with, and all it would add to a yard is a trip back to the workshop.
brewing, enchanting, villagers, the nethernone of it has anything to do with a status board.
fortune, silk touch, random drop countswhere the game rolls dice — four to nine lapis, seeds from one tuft of grass in eight — this drops a fixed amount. A player who breaks ten tufts and gets nothing concludes the server is broken.

Five things the library gets wrong had to be worked around rather than implemented:

  • The slot a player is holding. The protocol sends it as a short and valence reads one byte, so the packet is dropped and its own record of your hand never changes — the server reads one fixed slot for a whole session. The slot is decoded here instead.
  • The respawn packet. valence sends it after the chunks of the world it is respawning you into, and a respawn makes the client throw that world away. So a move between the two worlds goes through a tick of limbo, far enough out that no view distance can reach anything, and lands as an ordinary teleport once the respawn is long past.
  • Clicks that craft. valence validates every click against conservation of items and refuses any plain click that changes more than one slot. Taking a crafted result does both at once — four sticks appear out of two planks, across three slots — so a real client's craft is rejected as a duplication attempt, the event never fires, and the player watches the result sit in the slot and refuse to come out. The packet is read directly instead, after valence has had its say: its refusal applies nothing and resyncs the window, and the craft written afterwards is what the client is left holding. The one click valence would accept — a result taken without the ingredients reported — is charged for separately, or it would be free items for ever.
  • Stacks that cannot be picked up. A client puts Damage: 0 onto anything that can wear out the moment it handles it, and valence compares the client's idea of a stack with the server's whole when it validates a click. A hoe the server made without that tag is a hoe nobody can move: every click to pick it up is refused as a swap whose halves do not match, which is why the kit's tools in the pack sat there and would not come out. Anything damageable is now minted the way the client would mint it.
  • Inventories drifting. The client predicts every click and the server checks it afterwards; when they disagree, one item can look like two. The server sends each survival player what they are actually carrying about once a second, skipping anyone mid-click. Creative players are left alone: there the client is the authority, and it keeps what it has picked up on a cursor the server is never told about.

The password is a lock, not security

It is said in chat over a connection with no encryption — this server runs in offline mode, so there is none to negotiate — and if pcap is on, the box may be capturing it. It keeps the curious out of the board; it does not keep a determined attacker out. Do not reuse a password that matters anywhere else. Without minecraft.password there is no way in at all: everyone lands in the yard and nothing about the rules is served to anyone. That is the safe way round — a box shared without one gives nothing away.

The daemon's own binaries are built without the minecraft feature: it pulls in around 300 crates, which is not something a vulnbox should carry for a lamp. vulnboxd-mc is built with it and shipped beside them, and it is the only binary that has them.

Sharing it with other teams

This is a game server listening on the box you are defending, speaking a complicated binary protocol, built on a library whose last release was in 2023 — so it has had no security fixes since. It runs as its own process with nothing of vulnboxd's to reach, and minecraft.listen defaults to loopback, which is where it should stay unless you deliberately move it to your team's vpn.

Players also need a Minecraft 1.20.1 client, because that is the version the library speaks.

Running it during a game ​

The world is a toy, but it is a toy with people in it, and a deploy restarts it. Three things make that bearable, all of them for whoever runs the box rather than whoever is playing:

  • It says goodbye. A stop signal — systemd's, or a Ctrl-C — starts a fifteen-second countdown in the action bar, with a line in chat at fifteen, ten, five, three, two and one, then writes the yard and exits. A second signal goes at once. The shipped unit sets TimeoutStopSec=45, which has to stay above the countdown or systemd kills it mid-sentence.

  • Notices. A line written into world-notice.txt beside the yard is read out to everybody as a title and in chat, and the file is removed. No chat client and no password needed, so a deploy script can give warning on its own:

    sh
    echo "rolling out a patch in two minutes" > /var/lib/vulnboxd/world-notice.txt
  • Numbers. /stats answers a password holder with uptime, who is connected and where, services answering, blocks built, crops, furnaces alight, fluid blocks waiting, deaths and records. Anybody else gets silence, like any other unknown command — the public world must not hint that there is a command it is not telling you about. The same numbers are written to world-status.json beside the yard every fifteen seconds, and logged as one line every five minutes. The file names who has said the password, so it stays in the state directory, which is the daemon's own and readable by nobody else.