Files
minecraft_protocol/proxy-forwarding/bungeeguard.md
T
claude-timemachine cf6aa978d0 verify pass 3: FML token correction + BungeeGuard/cmd-providers from live source
archive.org Wayback was network-blocked; pivoted to live GitHub. Key fix: FML
host token is \0FML\0 (not \0FML2/3\0 — those are FMLNETVERSION ints on the
fml:handshake channel); modern Forge uses \0FORGE/\0FORGEn. Confirmed BungeeGuard
backend token check (lucko source), 4 cmd suggestion providers, 26.2 interval_select
worldgen-only, corrected 1.14 villager metadata layout.

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

70 lines
5.8 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` AND from BungeeGuard's own `BungeeCordHandshake.java` (Spigot module). The backend code uses `BUNGEEGUARD_TOKEN_NAME = "bungeeguard-token"`, iterates the GameProfile properties list to find the matching entry and removes it, then calls `tokenStore.isAllowed(bungeeGuardToken)` — a set-membership check against the configured `allowed-tokens` list. Missing token or failed check → kick with configurable message. Source: `lucko/BungeeGuard` `bungeeguard-spigot/src/main/java/me/lucko/bungeeguard/spigot/BungeeCordHandshake.java` (fetched 2026-06-19).
- See [bungeecord-legacy.md](bungeecord-legacy.md) for the underlying `\0`-delimited wire format BungeeGuard extends.