Files
minecraft_protocol/proxy-forwarding/bungeeguard.md
T
claude-timemachine a3d5f64ef5 verify pass: resolve VERIFY flags (corrections + citations + honest UNCONFIRMED)
Corrected real errors: several 1.7.x release dates, resource_pack_send version,
config packet ordering, structured-component count (56), PLAYER_LOADED (1.21.4),
entity_sound_effect field order. Confirmed+cited the rest; remaining ~19 items
re-marked UNCONFIRMED (third-party/ViaLegacy/26.2 internals unreachable from refs).

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

70 lines
5.6 KiB
Markdown

# BungeeGuard
A **hardening of [legacy forwarding](bungeecord-legacy.md)**, 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](velocity-modern.md) support.
It is **third-party** — not part of Spigot/Paper core or the Mojang protocol. The original is the [BungeeGuard plugin](https://github.com/lucko/BungeeGuard) (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:
```json
[
{ "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:
> ```java
> 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](velocity-modern.md) 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](README.md#comparison).
---
**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](https://github.com/lucko/BungeeGuard) — the original third-party plugin (token property + backend check). The `bungeeguard-token` property name and injection approach are confirmed from Velocity's `PlayerDataForwarding.java:52,175-196`. The upstream README/INSTALLATION.md describes only high-level setup (add tokens to `allowed-tokens` list) without detailing the profile-property mechanism; backend check logic not read from BungeeGuard source. <!-- UNCONFIRMED: backend plugin's property-inspection code verified only via Velocity's matching injection, not from BungeeGuard plugin source -->
- See [bungeecord-legacy.md](bungeecord-legacy.md) for the underlying `\0`-delimited wire format BungeeGuard extends.