Skip to content

Latest commit

Β 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 

Repository files navigation

πŸ›‘οΈ MiniMessage Exploit Protection β€” Paper Plugins

Global security template for any plugin using Adventure's MiniMessage API. Copy this into every repository. Check it on every release.


Why This Matters

When unfiltered player input reaches MiniMessage.deserialize(...), tags like these become live code:

Tag Risk
<red> Unintended formatting
<click:run_command:'/op ...'> UI manipulation
<hover:show_text:'...'> Confusing chat behavior

MiniMessage is safe by design β€” but only when trusted and untrusted data are kept strictly separate.

πŸ”‘ Golden Rule: All player input is untrusted data. Always.


Security Policy

Apply these rules without exception in every plugin:

  1. Never deserialize raw player messages directly.
  2. Only trusted sources may contain MiniMessage markup β€” e.g. config files, hardcoded plugin strings, admin-defined templates.
  3. Always escape player input before embedding it:
    MiniMessage.miniMessage().escapeTags(playerInput)
  4. If legacy color codes are allowed, convert them in a controlled, permission-gated way only.
  5. Treat PlaceholderAPI results as potentially untrusted whenever they may contain player-supplied text.

❌ Don't β€” Unsafe Pattern

String raw = playerMessage;                           // untrusted!
String format = "<gray><name>: " + raw;
Component c = MiniMessage.miniMessage().deserialize(format); // UNSAFE

Player input flows directly into deserialization β€” any tag they type gets executed.


βœ… Do β€” Safe Pattern

MiniMessage mm = MiniMessage.miniMessage();

String trustedFormat = "<gray><name>: {message}";  // trusted source only
String safeMessage   = mm.escapeTags(playerMessage); // sanitize untrusted input

String finalFormat = trustedFormat.replace("{message}", safeMessage);
Component c = mm.deserialize(finalFormat);           // safe

Trusted format and untrusted content are always kept separate.


Reference Implementation β€” Chat Handler

MiniMessage mm = MiniMessage.miniMessage();

// 1. Extract plain text from the chat event
String raw = PlainTextComponentSerializer.plainText().serialize(event.message());

// 2. Always escape player input first
String safe = mm.escapeTags(raw);

// 3. Optionally allow legacy colors β€” only for players with explicit permission
if (player.hasPermission("plugin.chat.rgb")) {
    safe = convertLegacyHexToMiniMessage(safe);
}
if (player.hasPermission("plugin.chat.color")) {
    safe = convertLegacyStandardToMiniMessage(safe);
}

// 4. Load trusted format from config; inject sanitized message
String trustedFormat = loadFormatFromConfig();
String mmFormat = convertLegacyToMiniMessage(trustedFormat)
    .replace("{message}", safe);

// 5. Render
Component out = mm.deserialize(mmFormat);
event.renderer((source, displayName, msg, viewer) -> out);

βœ… PR / Release Checklist

Before merging or releasing any feature that handles player-supplied text:

  • No direct deserialize(playerInput) calls anywhere in the codebase
  • Every untrusted string is escaped before being passed to deserialize
  • Trusted format strings and player content are clearly separated
  • Legacy / RGB color conversion is permission-gated and scoped correctly
  • PlaceholderAPI results have been reviewed β€” do any contain player input?
  • Tag-injection test cases have been run and pass

Minimum Test Cases

Run these inputs through every chat, GUI, or text feature before release:

# Input Expected Behavior
1 <red>Hello Tag rendered as plain text, not applied
2 <click:run_command:'/say hi'>Click Click event not registered
3 <hover:show_text:'X'>Text Hover event not registered
4 &aGreen Only works if legacy colors are enabled for that player
5 &#00FFAARGB Only works if RGB colors are enabled for that player
6 <red>&aTest<click:...> Mixed case β€” all tags neutralized

Pass criteria:

  • MiniMessage tags from player input are never executed
  • Tags appear as literal text or are stripped entirely
  • Only explicitly permitted legacy/RGB codes have any effect

Team Standard for New Plugins

Every new plugin in this organization must:

  1. Include this file in the repository root.
  2. Link to the security section from the main README.md.
  3. Check off the release checklist before every version tag.
  4. Test all chat / GUI / text features against the cases above.

Add this note to your code review template:

Untrusted input must never be parsed as MiniMessage tags.

Optional: Maximum Hardening

For plugins that require the highest security posture:

  • Disable MiniMessage entirely for player-facing text fields.
  • Use plain text only, with server-side color definitions.
  • Block messages containing detected tags and notify the sender.

Example player-facing message:

MiniMessage tags are not permitted in chat.

MiniMessage is powerful β€” keep it in trusted hands.

About

πŸ›‘οΈ Security template & best-practice guide for safely handling player input with Adventure's MiniMessage API in Paper/Spigot plugins β€” escape untrusted data, prevent tag injection.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors