Repository navigation
Releases: CaYatur/MinecraftFirewall
Release list
v1.7.5 — fixes the permanent black screen, and stops banning impatient players
Four things, all reported from a live server.
The black screen after a premium login
The plugin applied blindness with a duration of Integer.MAX_VALUE — effectively forever — and cleared it only when the firewall said to release. Asking to lock your name kicks you, and a player kicked while held is never released, so the effect went into their saved player data and came back with them. Every time.
Registering with a password cleared it, which is why only the premium route looked broken.
The effect is now short-lived and renewed by the plugin's own task for as long as somebody is actually held, so it follows that exactly and is gone within twenty seconds of them leaving it — whatever happens to the connection. It is cleared on quit too.
And because existing players are already carrying the broken one: a blindness longer than an hour, which nothing can produce any more and no potion in the game grants, is cleared when they join. Update the plugin from the Protection page and your stuck accounts fix themselves on next join.
A temporary effect must not be able to become a permanent one.
Getting banned for reconnecting too fast
The login limit is five in thirty seconds, the sixth earned a strike, and five strikes is a six-hour machine-wide ban — so somebody impatiently mashing reconnect banned themselves in about a minute, having done nothing but try to join.
Refusing the connection is the whole response to going too fast. It no longer counts towards anything.
What still escalates is the thing a player cannot do however fast they click: arriving under a series of different names. That is a list being worked through. The names tried while being refused are now remembered, too — before, every one was thrown away with the connection that carried it, so an address burning through names while rate-limited looked like an address using one.
Volume alone is still handled, separately, by admission control.
Messages that stayed white
Your configuration file wins over the built-in defaults — which is right, it is your file — but it means improved wording never reaches an existing install. The newly coloured login messages arrived for new installations and for nobody else.
Settings → "Update messages to the latest defaults" brings them up to date. It only touches a message whose text is one this project itself shipped, which is proof no person chose it. Anything you wrote — a translation, your own wording — is left exactly as you wrote it, and the reply tells you how many were kept.
The list of what was shipped is generated from the release tags rather than kept by hand, because a hand-kept list rots and fails in the worst direction: quietly replacing somebody's own words.
Premium accounts had no join date
The Players page showed a dash where a date belonged, because only the password path recorded one. That reads as missing data rather than as a different kind of account.
676 tests.
v1.7.4 — fixes SkinsRestorer, and stops asking the owner for a password
Three things, all from a live server.
SkinsRestorer (and anything else that asks)
[SkinsRestorer] This plugin is running in PROXY mode!
Two settings can carry IP forwarding, and only one of them may be on. Leaving bungeecord: true in spigot.yml beside proxy-protocol: true in paper-global.yml doesn't double anything — it makes your server announce to every plugin that it sits behind a BungeeCord network when it doesn't, and they believe it. SkinsRestorer switches into proxy mode and stops working. It isn't the only plugin that asks.
Set my server up for this now clears the other one in the same step, and the confirmation shows both lines before anything is written.
If something that worked before stopped working when you turned forwarding on, this is almost always why.
The premium lock asked for a password on the join that granted it
The identity decision is made before the premium challenge runs — so on the connection where you finally prove you own a name, it still said "ask them to register". That is backwards, and this project has exactly one promise: the genuine owner is never shown a password prompt.
The decision is now re-read after the challenge, because the challenge may have just changed the answer.
The premium messages are coloured
They were the last ones still plain white while everything around them had colour.
One bug caught on the way
Writing the two-file edit needed a test for "the same key exists under a different parent" — and that test failed. Apply was re-finding the line by its text and taking the first match, throwing away the block scoping that planning had established. A config with proxy-protocol under two sections would have had the wrong one rewritten.
Edits now carry the planned line index and refuse if what's there is no longer what you approved.
666 tests. Verified against a real 144-line Paper config and a real spigot.yml: exactly the intended lines change, line endings and comments untouched.
v1.7.3 — fixes the 1.7.2 login break, and configures forwarding on both sides
If you are on 1.7.2, update — it broke every login.
The disconnect (my bug, not IP forwarding)
Internal Exception: io.netty.handler.codec.DecoderException:
Received unknown packet id 21
21 is the Play-state custom-payload id. Packet ids live in per-state namespaces, and Finish Configuration is what moves the server into Play — but 1.7.2's plugin bridge wrote its "hold this player" instruction to the server just before forwarding that packet. So the server read a Play id against Configuration's namespace, couldn't decode it, and dropped the connection. Every login, for everyone.
Fixed, with a test that pins the ordering rather than the symptom.
Worth saying plainly: your IP forwarding was working. Your own log has
xTest (/159.146.99.204:52749) lost connection
— your real address, delivered to the server. Two unrelated failures arrived together and looked like one problem.
Forwarding now sets up both halves, and rescues itself
The Servers page will configure your server for you. Pick a mode, press Set my server up for this: it finds the file, shows you the exact line it would change, and writes nothing until you approve that line. Exactly one line changes — verified byte-for-byte against a real 144-line Paper config — and the original is kept beside it.
And if the two settings ever disagree, the firewall notices. A server not told to expect the forwarding data reads it as the first Minecraft packet, can't decode it, and drops every connection — leaving the server unjoinable for a reason invisible from inside the game. After three connections that die before the server has said a word, forwarding switches itself off, the log names the setting you need, and people can play again. Nothing is written to disk, so the next restart tries again rather than silently disabling something you configured on purpose.
About vanilla
You asked for a method that works on vanilla too. There isn't one, and this is checked rather than assumed — I searched a real Mojang server jar for any forwarding setting. Vanilla has none. It reads the socket it is talking to and cannot be told otherwise; the only forwarding-adjacent key it has is prevent-proxy-connections, which does something different.
That is vanilla's limitation, not this firewall's. Paper and Spigot both have a setting, and the button above will set it. On vanilla, leave forwarding off — the panel now says so specifically instead of failing.
664 tests. Windows 10/11, x64.
v1.7.2 — the optional plugin: held players cannot be hurt, and the commands turn blue
The optional server plugin
There is one thing a firewall in front of a server cannot do, and it is the thing people notice: stop a held player being hurt. A player waiting at the login prompt is genuinely standing in your world, and only the server decides their health. Until now the best available was to notice the damage and disconnect them before they died — a reconnect instead of an inventory, which is better than nothing and not the same as fixing it.
This release adds the part that goes inside the server.
The Protection page finds your server and installs it for you. It shows you the exact path first and writes nothing until you approve that path. About four kilobytes, compiled against the Minecraft 1.8 API using only calls that have existed since then, so one file works on every version this firewall supports and on versions that do not exist yet. The source is in plugin/ and is short enough to read in a couple of minutes.
With it installed, a player at the login prompt:
- cannot be hurt at all — not by mobs, falling, drowning, the void, fire, or hunger
- cannot be moved by anything in the world
- sees
/login,/registerand/premiumin blue, as real commands, and finds them in/help— which is where somebody actually works out how to claim their name
Everything still works without it. Nothing waits for it, no message depends on it, and a server without it behaves exactly as it did before it existed. Identity.UseServerPlugin turns it off entirely.
It never decides anything
Who is held, and for how long, is the firewall's judgement alone; the plugin holds whoever it is told to hold. A plugin that decided for itself would be a second implementation of the login rules, free to disagree with the first. It is also deliberately not allowed to look at the world — that would be the shadow world model this project already rejected for the anti-cheat, in a place where it is even harder to reason about.
The channel is closed at both ends
Instructions travel on the player's own connection and the format has no field for a name, so a message cannot be aimed at anybody else. The plugin cannot tell an instruction the firewall injected from one a player sent — so the firewall drops anything a client sends on that channel, unconditionally, not as an inspection setting. Tested with a byte-identical forged instruction.
Installing is the careful part
It writes into software this project does not own, and the server folder is found by a heuristic. So: the exact absolute path is approved before anything is written, a missing plugins folder is treated as evidence the wrong directory was found rather than something to create, and the only filename ever written or deleted is our own.
One thing to be aware of: the plugin builds, packages and installs correctly, and that whole path is verified. It has not been loaded on a live Minecraft server from here — there isn't one on the build machine. If it misbehaves on yours, the source is right there and it is forty lines.
646 tests. Windows 10/11, x64.
v1.7.1 — fixes a 1.7.0 disconnect, and colours the login messages
If you are on 1.7.0, update. It disconnects players for reading a long message.
The disconnect
Minecraft's compressed frame format carries a declared uncompressed length, and both ends enforce a rule about it that is easy to miss: a payload at or above the negotiated threshold must be compressed, one below it must not be. Every packet this proxy composed itself was sent uncompressed, on a belief written into the code as a comment — that the format allowed it whatever the threshold was.
It does not. The explanation of what locking your name means is 312 bytes and the default threshold is 256, so asking the question kicked you:
Internal Exception: io.netty.handler.codec.DecoderException:
java.lang.IllegalStateException: Actual uncompressed size 312 is greater than threshold 256
The threshold is now read from the backend's own Set Compression rather than assumed, and nothing is sent before it is known. All three encodings are picked correctly, boundary included — exactly-at-threshold is legal compressed and illegal uncompressed, which is where an off-by-one disconnects one player in a million and is never found.
Coloured login messages
Using the codes every server owner already knows — &c red, &l bold, &r reset; § works too. Every message is still yours to edit.
An ampersand only counts as a code when what follows is a lowercase letter or a digit, so "Q&A" in your own text survives untouched.
A premium lock now survives a restart
Setting one told the player, in those words, that only their account could ever use the name again. It lasted until the next restart — and failed open: the name went back to being usable by anyone, and the UUID pin recorded for it sat unused, because a pin is only consulted while the name is currently locked.
That is fixed for both ways a lock is made at runtime: a player proving their account, and an administrator clicking Lock on the Players page. Locks that your configuration file declares stay unpersisted, because the file owns those and a cache would quietly overrule the file you edited.
A backend that hangs up is no longer a stack trace
It was logged as an unhandled error, forty lines, at error level, naming nothing you could act on. Now it is one line — and if IP forwarding is on, it says so, because a server not configured to expect the header drops every connection the moment it arrives and that looks exactly like this.
627 tests. Windows 10/11, x64. Nothing to install first.
v1.7.0 — the login prompt stops lying, and your server sees real IPs
Everything in this release came from someone actually playing on a server in front of it, not from a test suite. The tests were green throughout.
Waiting at the login prompt no longer looks broken
Holding an unauthenticated player was never the problem — the server genuinely never saw them move. Everything they could see was wrong.
- Minecraft predicts movement on the client and only corrects it when the server disagrees, so a held player walked around their own screen and then snapped back.
- Their real coordinates sat on the HUD for anyone watching, before they had proved they owned the name.
- Mobs went on hitting them while they read the prompt, and they could die at it.
- The prompt was one line of chat, which scrolls away while you are in your inventory.
- The commands it told them to type rendered red, because the backend has never heard of them — the firewall answers first.
Now: the client is told where it is, repeatedly, at the origin rather than at their real position, and put back exactly where the server has them the moment they log in. The prompt goes across the middle of the screen as well as into chat. The words work without a slash, so nothing renders as an error. Someone registering is offered the premium route, which until now could only be found from the far side of the prompt asking for a password.
Every line of it is configurable, the on-screen title included.
Damage is the one this cannot fix outright: a firewall in front of a server cannot stop a creeper. What it can do is notice — a held player who starts taking damage is disconnected before they die. A kick costs a reconnect; a death costs an inventory.
Your server can finally see who is connecting
Every player in the server log was 127.0.0.1. All of them, always — so banning an IP there banned the proxy, which is everyone.
Pick PROXY protocol or BungeeCord-style forwarding per server, on the Servers page. The offline UUID that BungeeCord forwarding carries is checked against real UUIDs from a live server's own log; getting that wrong would not look like a bug, it would look like everyone's account being wiped.
Neither is on by default and neither is safe alone — your backend has to expect it, and its port has to stay on loopback. The Security check page already tests exactly that.
A Players section
Pick a server and see everyone it knows: who registered and when, when they were last seen, from where, and the itemised reasons that address looks suspicious — the same weights the log prints. Open one and their whole history is there.
Five actions, each asking first: forget trusted addresses, reset a password, lock a name to its Minecraft account, unlock it, remove it.
Players can also change their own password in game with changepassword <current> <new>. The current one is required even from a trusted address, because a household shares an address.
New Minecraft versions, without you doing anything
A version this build has never seen is now learned at runtime. What makes that safe is the refusal, not the fetching: before a single learned table is believed, the same source has to reproduce every table this build generated from Mojang's own data generator — all twelve, exactly. One disagreement anywhere and the whole fetch is abandoned.
Malformed packets, refused before the server parses them
What PacketFixer-style plugins do, a step earlier — a plugin runs after the decoder, and the decoder is where the damage happens.
Also fixed
- Connecting by raw IP no longer counts as announcing a foreign domain, and the count is now a window rather than a tally. Previously a server's own owner scored full bot weight on every join they ever made, with no way back short of a restart.
- Somebody who logged off wounded is no longer kicked on sight and told something attacked them. Damage is a fall, not a level.
- A save can no longer silently switch IP forwarding back off — caught by its own regression test before it shipped.
Honest about the rest
A long list was asked for: an Identity Engine, a DDoS engine, a Sonar/LimboFilter-class bot engine, an anti-cheat. docs/requested-features.md goes through all of it, item by item — done, added, feasible-but-not-built, or not possible from a proxy, with the reason each time.
The short version: a verification limbo means implementing enough of a Minecraft server to hold a client in a world, per version. An anti-cheat cannot see the world from here — flight needs to know whether there is a block underneath somebody. Alt-account correlation is refused on purpose, because "these names used this address" is equally true of a family and a games café.
594 tests. Windows 10/11, x64. Nothing to install first — the .NET runtime is bundled.
v1.6.0 — twelve Minecraft versions, and the firewall finds your server
Every Minecraft version from 1.20.2 onward now works with CaYaDev-Check, and the firewall works out what your own server is.
Version coverage went from one to twelve
1.5.0 shipped tables for 1.21–1.21.11. This adds 1.20.2, 1.20.3/1.20.4, 1.20.5/1.20.6 and one future release — protocols 764 through 775.
Mojang's data generator only emits packet IDs from 1.21 onwards, so the older ones come from minecraft-data. That is a second-hand source, and this project does not accept packet IDs it did not generate — so it is not simply trusted. The script first reproduces every Mojang-generated table from minecraft-data and refuses to go further unless all of them match exactly. They did, on all six versions where both sources exist.
What about 1.8, 1.16.5, ViaVersion?
They connect and play, and always could. The firewall relays a connection it cannot inspect rather than refusing it — a client on 1.8 coming through ViaVersion joins normally.
What those clients cannot do is be held for a password: reading their chat needs packet tables, and below 1.20.2 Minecraft had no Configuration protocol state and no clientbound system-chat packet, so a held player could not even be told why. That is a structural limit, not a missing lookup table.
For names you actually care about, lock them to a Microsoft account instead — that works on every version, because it happens during login rather than in chat.
The firewall finds your server and tells you what applies
The Security check now reports your server's version and whether ViaVersion or ViaBackwards is installed — the thing that most changes what the login system can do for you, and the thing nobody thinks to mention.
Reading the listening process's command line was the obvious approach and it does not work: the ordinary way to start a server is java -jar paper.jar from inside its own folder, which names no directory, and a Java process's own image path points at the JVM. So it uses the port as the fingerprint instead — every server has a server.properties saying which port it listens on, so the folder whose server.properties matches your backend port is your server, wherever it lives.
Downloads
| File | Use it if |
|---|---|
MinecraftFirewall-1.6.0-setup.exe |
You want it installed properly. Start here. |
MinecraftFirewall-v1.6.0-win-x64.zip |
You would rather unzip it somewhere and run it yourself. |
Everything from v1.5.0 is included, including the anomaly-model actions.
503 automated tests pass. Backend detection verified against a real Paper server: found by port alone, version read correctly.
v1.5.0 — every 1.21 version works, and the model can act
Fixes the bug that stopped players joining when CaYaDev-Check was on. If you are running 1.4.0, take this one.
Every 1.21 version works now
The registry held exactly one version — 1.21.11 — so with the login system switched on, anyone on a different client was refused with "This client version is not supported by the server's protection layer." That is every player who is not on the newest release.
Verified packet tables now ship for 1.21 through 1.21.11 (protocols 767–774), generated the only way this project accepts: each version's own server jar run through Mojang's data generator. They confirm why guessing was never an option — serverbound chat is 0x06 on protocol 767, 0x07 on 768–770 and 0x08 from 771, and clientbound system chat moves four times across the same range.
The generator ships in tools/, so a new Minecraft release is two commands rather than a code change:
python tools/generate-protocol-tables.py work-dir
python tools/apply-protocol-tables.py work-dirWhat about other versions, and ViaVersion?
Ordinary players connect on any version, and always could — including anything arriving through ViaVersion or ViaBackwards. The firewall relays a connection it cannot inspect rather than refusing it.
Two things still refuse on an unverified version, and deliberately: a premium-locked name, and a password-registered name arriving from a new address. Skipping either would mean anyone could claim a protected name just by sending an odd version number — which is precisely the attack those features exist to stop. Server-wide registration is a blanket policy with no particular identity at stake, so those connections now go through and the log says what could not be enforced.
The kick message was also misleading: it read as though the whole server were version-locked. It now says other players are unaffected.
The anomaly model can act, not just warn
Choose what a repeated anomaly causes, from the Protection page:
| Action | What happens |
|---|---|
| Report (default) | Written to the log, nothing else. |
| Score | Counts towards that address's bot score, alongside the behavioural signals. |
| Require re-authentication | Their next connection must log in again, even from an address they have used before. |
| Throttle | That address's connection limits are tightened for a while. |
| Ban | A real firewall ban for the configured duration. |
It stays proportionate by construction. Nothing happens on a single odd session — sessions are odd for innocent reasons constantly. Nothing happens at all until the baseline has been settled for an hour, because a freshly trained model is least reliable exactly when it is newest. And every action lands on the address's next connection rather than the one just scored: the model judges a whole session, so by the time it has an opinion the session is over. The worst it can do to somebody mid-game is nothing.
Downloads
| File | Use it if |
|---|---|
MinecraftFirewall-1.5.0-setup.exe |
You want it installed properly. Start here. |
MinecraftFirewall-v1.5.0-win-x64.zip |
You would rather unzip it somewhere and run it yourself. |
Upgrading? The Protection page notices a configuration that predates newer settings and offers to add them, leaving your servers and protected usernames untouched.
503 automated tests pass. Verified against a running service: protocols 767, 771, 774 and an unknown 999 all admitted.
v1.4.0 — registration that actually holds players, and VPN settings in the UI
The two things you could not do from the control panel — one of which was quietly undoing itself.
CaYaDev-Check now actually holds a player
The login system was opt-in per player, and it did not hold anyone. A name with a password connecting from an unknown address had to send /login as its first chat message — and every other packet flowed through untouched. The player was on your server, walking around and breaking blocks, while the proxy waited politely for a password. There was no server-wide mode at all, so "turn on the login system" was not something anyone could do.
Now it is, and it holds. Switch on Require every player to register and log in and a player joins frozen: movement, hitting, placing, item use and container clicks are refused and never reach your server. Only chat gets through, which is how they type the command.
/register <password> → first time
/login <password> → afterwards, from a new address
They are prompted on arrival and reminded periodically, because Minecraft chat scrolls and one message at join time is gone by the time anyone looks. Keep-alives keep flowing while they are frozen, so your server does not time them out mid-prompt. Once they authenticate, their address is remembered.
Registering and logging in are treated differently on purpose. Choosing a password gets as many attempts as the timeout allows — there is nothing to guess, and kicking somebody for a typo would be hostile for no gain. Proving an existing password gets one, because a wrong password against a name that already exists is the classic stolen-credential probe.
Premium-locked names skip the whole thing. Their Microsoft account has already proved who they are; a password on top would mean making the real owner prove themselves twice.
VPN settings are in the UI — and no longer deleted
They were not merely missing from the control panel: saving the Servers page deleted them. A save replaces the whole server list, the editor only knew about six fields, and VpnPolicy and UseDatacenterList were dropped — the service then silently fell back to its compiled default. Anyone who had set block for everyone lost it the first time they renamed a server, with nothing anywhere to say so.
Both are now read, written and editable per server: the policy as a three-way choice (log only / protected usernames only / everyone) and the wider datacentre list as a checkbox.
Also
The VPN dropdown shipped in an unreadable state on first attempt — a ComboBox paints its own chrome from system colours, so it came out light-on-light against this theme. It is properly templated now. Every screenshot in the README has been retaken from the installed service, including the Turkish rendering of both pages.
Downloads
| File | Use it if |
|---|---|
MinecraftFirewall-1.4.0-setup.exe |
You want it installed properly. Start here. |
MinecraftFirewall-v1.4.0-win-x64.zip |
You would rather unzip it somewhere and run it yourself. |
Upgrading from an older version? The Protection page will notice your configuration predates these settings and offer to add them — your servers and protected usernames are left exactly as they are.
490 automated tests pass.
v1.3.2 — upgrading no longer leaves the service stopped
A fix for upgrades. If you installed 1.1.0 or later, take this one.
Upgrading no longer leaves the service switched off
Installing over an existing version left the machine unprotected, and everything about it looked like it had worked. The setup stops the service before replacing files, then registers it — and sc create fails outright when the service already exists, so the registration reported failure, nothing started it again, and the only sign was a status page nobody had open.
An already-registered service is now reconfigured rather than recreated, which also handles the case that makes the situation arise: an install path that has changed. The service's registry ImagePath stays quoted through the upgrade, so the unquoted-service-path hazard the first install avoids is not quietly reintroduced.
Verified on a real install: service Stopped before, Running after, still set to start with Windows, path still quoted.
Downloads
| File | Use it if |
|---|---|
MinecraftFirewall-1.3.2-setup.exe |
You want it installed properly. Start here. |
MinecraftFirewall-v1.3.2-win-x64.zip |
You would rather unzip it somewhere and run it yourself. |
Everything from v1.3.1 and v1.3.0 is included: escalating bans for repeat offenders, the player-initiated /premium name lock, automatic blocking of server-indexing crawlers, and the repair for configurations written by older versions.
476 automated tests pass.