Files
minecraft_protocol/proxy-forwarding/bungeeguard.md
T
claude-timemachine d73c1c9537 minecraft_protocol: foundation + per-version protocol docs 1.7.10->26.2
8 topical docs (overview, data types, lifecycle, handshake, status/ping,
login+encryption, configuration, version-differences) + proxy-forwarding set
+ 16 per-version release-line docs, sourced from minecraft.wiki, ViaVersion
(source + commits), minecraft-data, node-minecraft-protocol, Velocity, BungeeCord.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 14:15:32 +02:00

5.2 KiB

BungeeGuard

A hardening of legacy forwarding, not a new transport. Legacy's fatal flaw is that anyone who can reach the backend port can forge the \0-delimited identity string and connect as anyone. BungeeGuard fixes that by smuggling a shared secret token into the forwarded data; the backend plugin refuses the login unless the token matches. It bridges backends that only support legacy forwarding up to "can't be trivially forged," without requiring modern/Velocity support.

It is third-party — not part of Spigot/Paper core or the Mojang protocol. The original is the BungeeGuard plugin (lucko), installed on both the proxy and every backend.

How it works

BungeeGuard reuses the exact legacy wire format — the four \0-separated handshake-address segments (realHost\0clientIP\0uuidNoDashes\0propertiesJSON). The trick is in the properties JSON: it appends one synthetic game-profile property named bungeeguard-token whose value is the shared secret.

A game-profile property is a {name, value, signature?} object — the same shape as the Mojang textures property. BungeeGuard injects an extra one:

[
  { "name": "textures",         "value": "...", "signature": "..." },
  { "name": "bungeeguard-token","value": "<the shared secret>" }
]

On the backend, the BungeeGuard plugin (replacing the stock legacy parser) reads the player's properties, finds the bungeeguard-token entry, and compares its value against the secret it was configured with. Match → accept and strip the token; mismatch or absent → reject the connection. The real IP/UUID/skin are still taken from the same legacy segments.

Velocity has this mode built in (player-info-forwarding-mode = "legacy" is bare legacy; "bungeeguard" adds the token). Its source builds the address identically to legacy and just adds the token property:

private static final String BUNGEE_GUARD_TOKEN_PROPERTY_NAME = "bungeeguard-token";
...
final GameProfile.Property property = new GameProfile.Property(
    BUNGEE_GUARD_TOKEN_PROPERTY_NAME,
    new String(forwardingSecret, StandardCharsets.UTF_8),
    "");
return createLegacyForwardingAddress(serverAddress, playerAddress, profile,
    properties -> ImmutableList.<GameProfile.Property>builder()
        .addAll(properties).add(property).build());

Velocity/proxy/src/main/java/com/velocitypowered/proxy/connection/PlayerDataForwarding.java:52 (name) + :175-196 (createBungeeGuardForwardingAddress)

So a BungeeGuard handshake address is exactly a legacy one with one extra property in the JSON array.

Security: better than legacy, weaker than modern

bare legacy BungeeGuard modern (Velocity)
Forge-able by anyone reaching the port Yes No (needs the token) No (needs the secret)
Secret/token crosses the wire n/a Yes — token sent in cleartext inside the connection No (only an HMAC crosses)
Tamper-proofs IP/UUID/props No No — only gate-keeps; the identity fields are still unsigned plaintext Yes — whole payload is HMAC'd
Backend requirement core bungeecord:true BungeeGuard plugin Paper/Fabric modern support

The crucial limitation: BungeeGuard is authentication of the connection, not authentication of the payload. It proves "whoever sent this knows the token," but the IP/UUID/properties themselves are not signed — once the token check passes, those fields are trusted as-is, exactly like legacy. And the token is transmitted in cleartext in the handshake. So:

  • If the connection between proxy and backend is not encrypted/private, a network observer can lift the token and then forge freely.
  • The token is a single shared value across all backends; leaking it from any one host compromises all.

It is a real improvement over bare legacy (you no longer rely solely on a firewall) and the right choice for a backend that cannot do modern forwarding. But when every backend supports it, modern/Velocity forwarding is strictly stronger — it signs the payload with an HMAC and never puts the secret on the wire.

When to use it

  • A backend (old Spigot, a plugin platform, a Fabric server without modern-forward support) only speaks legacy, and you can't firewall it tightly enough to trust bare legacy.
  • You're migrating a BungeeCord/Waterfall network and want hardening without switching every backend to Velocity-native forwarding.

Otherwise prefer modern. See the README comparison table.


Sources

  • Velocity/proxy/src/main/java/com/velocitypowered/proxy/connection/PlayerDataForwarding.java:52 (bungeeguard-token property name), :175-196 (createBungeeGuardForwardingAddress — legacy address + token property), :154-173 (shared createLegacyForwardingAddress it builds on).
  • lucko/BungeeGuard — the original third-party plugin (token property + backend check).
  • See bungeecord-legacy.md for the underlying \0-delimited wire format BungeeGuard extends.