All versions
BUG-1 — Deprecated
BUG-2 —
BUG-3 —
BUG-7 —
1.2.0
Release
OPShield 1.2.07 months ago 17
Compatibility
Minecraft: Java Edition
1.21.x
Platforms
Bukkit
Paper
Purpur
Spigot
Changes
[1.2.0] — 2026-03-15
🐛 Bug Fixes (7 fixes)
BUG-1 — Deprecated ChatColor API replaced with Adventure LegacyComponentSerializer
- Root cause:
getMsg()usedChatColor.translateAlternateColorCodes('&', msg), which is deprecated in Paper 1.21 and produces build warnings. - Fix: Replaced with
LegacyComponentSerializer.legacyAmpersand().deserialize(raw). AllsendMessage()calls now passComponentobjects. Added helper methodssend(sender, key)andsendReplaced(sender, key, ...)to centralise message dispatch.Bukkit.broadcastMessage()replaced withBukkit.broadcast().
BUG-2 — allow_op_reload: false had no effect on console
- Root cause: The reload handler checked
!allowOpReload && sender instanceof Playerfirst, so console senders (who are notinstanceof Player) skipped the check entirely and were always permitted. - Fix: The behaviour is now explicit and documented:
allow_op_reloadintentionally controls in-game players only. Console is always allowed to reload. Added a clear comment in code and updated the README to document this intent. The help message was also updated to reflect the correct usage.
BUG-3 — savePersistentData() blocked the main thread with I/O
- Root cause: Every wrong password attempt triggered
savePersistentData()synchronously on the main server thread, writing todata.ymlvia file I/O which can stall the tick cycle on loaded servers. - Fix:
savePersistentData()now snapshots the maps on the main thread (thread-safe, fast), then dispatches the actual file write viaBukkit.getScheduler().runTaskAsynchronously().onDisable()still saves synchronously because async tasks may not execute during shutdown.
BUG-4 — No way to manually unlock a locked-out player
- Root cause: When a player was locked out there was no command to lift the lockout. Admins had to either wait for the timer or delete
data.ymlentirely. - Fix: Added
/opshield unlock <player|ip>command. Accepts either a player name (matched case-insensitively) or an IP address (dots automatically converted to underscores to match the storage key). ClearsfailedAttempts,lockoutTimestamps, andlockoutCountfor the matching key. Requiresopshield.unlockpermission (default: op). Newunlock_successandunlock_not_foundmessage keys added to all language files.
BUG-5 — Lockout reset allowed infinite brute-force with fixed delay
- Root cause: When a lockout expired,
isLockedOut()cleared bothlockoutTimestampsandfailedAttempts, resetting the counter to zero. An attacker could attemptmax-attempts - 1wrong passwords, wait out the lockout, and repeat indefinitely without ever receiving a longer penalty. - Fix: Introduced
lockoutCount(persisted indata.ymlaslockout_count). Each time a lockout is triggered, the count increments and the duration doubles:lockoutDurationMinutes * 2^(count-1), capped at 24 hours. On successful authentication thefailedAttemptsandlockoutTimestampsare cleared, butlockoutCountis preserved so repeated abuse continues to receive longer lockouts. The unlock command (BUG-4) clears all three maps.
BUG-6 — Command block / server-console bypass is documented (not a code bug)
- Root cause: Lockout and whitelist checks are gated on
sender instanceof Player— command blocks and other plugins dispatching/opbypass these checks by design because they have no IP address. - Fix: Added explicit documentation in README explaining this is intentional. Server administrators are advised to restrict physical/panel access to prevent console-level abuse.
BUG-7 — CommandMap reflection failure was fully silent
- Root cause: If
getCommandMap()reflection failed (e.g., custom server forks, future API changes), the exception was caught by a barecatch (Exception ignored)block with no log output, making diagnosis impossible. - Fix: Replaced
ignoredwith agetLogger().fine(...)call that logs the failure at FINE level with the command name and exception message. This appears in debug logs without spamming the console under normal operation.
✨ New Features
/opshield unlock <player|ip>— manually clear lockout for a player or IP (opshield.unlockpermission)/opshield(no args) — now shows a brief help line instead of an error/opsalias added for/opshield- Exponential backoff: lockout duration doubles per repeat offender (capped at 24 h)
- Language file defaults auto-merged from jar — new keys appear automatically in existing language files without requiring users to recreate them
🔧 Other Changes
pom.xml: version → 1.2.0; switched dependency fromspigot-apitopaper-api(Paper bundles Adventure — no extra shade required); compiler source/target bumped from Java 17 → Java 21 (maven.compiler.release)plugin.yml: version → 1.2.0; addedopshield.unlockpermission; updated descriptions; added/opsalias- All 3 language files (
en,vn,ru): addedunlock_successandunlock_not_foundkeys; updatedlockout_messageto mention increasing duration
Projects on Modrinth are automatically available through a Maven repository for use with JVM build tools such as Gradle. To learn more about the Modrinth Maven API, click here.
Note: When available, you should use the creator's maven repo instead as it will have transitive dependency information that the Modrinth Maven API does not. You may also end up with duplicate dependencies if you use a mix of Modrinth and non-Modrinth Maven repositories for your dependencies, because the group identifier will be different when served through the Modrinth Maven API.
Maven coordinates:
Version ID:
build.gradle:
repositories {
exclusiveContent {
forRepository {
maven {
name = "Modrinth"
url = "https://api.modrinth.com/maven"
}
}
// forRepositories(fg.repository) // Uncomment when using ForgeGradle
filter {
includeGroup "maven.modrinth"
}
}
}
// Standard Gradle dependency
dependencies {
implementation "maven.modrinth:Bv67n40d:GxKRLURV"
}
// Legacy Loom dependency
dependencies {
modImplementation "maven.modrinth:Bv67n40d:GxKRLURV"
}

