OPShield
2.0.0Protects your server from OP/admin abuse with console-only OP (via password) and optional admin command restriction.
2.0.0latestOPShield 2.0.0Jun 4, 2026 [2.0.0] - 2026-06-03 — Thread-Safety & Security Overhaul
This release resolves all known thread-safety bugs identified through a full code audit. No configuration keys were added, removed, or renamed. Existingconfig.ymlanddata.ymlfiles are fully compatible — no migration required. ---1.9.1OPShield 1.9.1May 23, 2026 [1.9.1] - 2026-05-23 — Bug Fix Release
This is a patch release that resolves six bugs identified through code review. No configuration keys were added, removed, or renamed. Existingdata.ymlandconfig.ymlfiles are fully compatible — no migration needed. ---
#### FIX [Critical] — Double timestamp in every audit log entry - Root cause:Bug Fixes
OPShield.audit()manually prepended a formatted timestamp to the message string before passing it toAuditLogger.log(). However,AuditLogger.buildLine()also wraps every line with its own timestamp, resulting in output like:
This affected every audit entry since v1.6.0. - Fix: Removed the manual timestamp construction from[2026-05-23 10:00:00] 2026-05-23 10:00:00 | Configuration reloaded.OPShield.audit(). Timestamp formatting is now owned exclusively byAuditLogger.buildLine(). Audit lines now read correctly:
- Impact: Existing rotated log files ([2026-05-23 10:00:00] Configuration reloaded.audit.log.1etc.) retain the old double-timestamp format. New entries written after the upgrade are clean. No action required on existing files. #### FIX [High] —auth_session_grantedandauth_session_activemessages were never sent - Root cause: The language keyauth_session_grantedwas introduced in v1.9.0 but the correspondingsend()call was absent from bothcompletePrivilegeCommand()(session grant path) andhandlePrivilegeCommand()(session reuse path). Players had no way to know a session existed or how much time remained. - Fix: -completePrivilegeCommand()now callssendReplaced(player, "auth_session_granted", "minutes", …)immediately after the session is stored inauthenticatedSessions. -handlePrivilegeCommand()now callssendReplaced(player, "auth_session_active", "minutes", …)(with the remaining minutes) on the session-reuse fast-path. - New language key:auth_session_activeadded toen.yml,vn.yml, andru.yml. #### FIX [Medium] — Shadow-ban decoy messages lost all colour formatting - Root cause: The fake-success message pipeline was:PlainTextComponentSerializer.plainText().serialize(colorize(fakeMsg))colorize()correctly parsed&a,&c, etc. into an AdventureComponentwith colour, butPlainTextComponentSerializer.plainText().serialize()then stripped every colour code before wrapping the result in a hard-codedNamedTextColor.YELLOW. Language-file formatting was silently discarded. - Fix: Removed the unnecessary serialise/re-wrap chain. TheComponentreturned bycolorize(fakeMsg)is now sent directly:
Server owners can now apply any Adventure-compatible colour codes (player.sendMessage(colorize(fakeMsg));&a,&c,&l, etc.) in their language files and have them rendered correctly. - Cleanup: Removed now-unused importsNamedTextColor,PlainTextComponentSerializer, andComponentfromOPShield.java. #### FIX [Medium] —LockoutManager.cleanupExpired()discarded backoffcountearlier thancount_decay_hoursintended - Root cause:cleanupExpired()removed any record whoseexpiryMs <= nowANDattempts == 0. When a lockout expired naturally,isLockedOut()setexpiryMs = 0andattempts = 0(intentionally preservingcountfor exponential backoff). On the very next cleanup tick (≤ 60 s later), the cleanup predicate matched and deleted the entire record, includingcount. Thesecurity.lockout.count_decay_hoursconfig key had no practical effect: the backoff counter was always reset within one cleanup cycle after each lockout expired. - Fix:cleanupExpired()now accepts adecayMsparameter (derived fromlockoutDecayHoursinOPShield). A record is only removed when both conditions are true: 1. No active lockout and no in-progress attempts. 2. The time elapsed sincelastLockoutAtMsexceedsdecayMs. Exponential backoff now behaves as documented. - API change (internal):LockoutManager.cleanupExpired()→cleanupExpired(long decayMs). No external callers; internal call site updated inOPShield.cleanupRuntimeState(). #### FIX [Medium] —/opshield reloadblocked byallow_op_reload: falseeven for players with explicitopshield.reloadpermission - Root cause: InhandleManagementCommand(), theallow_op_reloadflag was evaluated before the permission check. A player with an explicitopshield.reloadgrant from a permissions plugin was still denied whenallow_op_reload: false, making the permission effectively meaningless. - Fix: The permission check (opshield.reload) now runs first. Theallow_op_reloadflag is applied only as a secondary restriction after the caller is confirmed to hold the permission. Console senders are unaffected. #### FIX [Low] — IP tracking unreliable behind BungeeCord / Velocity (documentation + debug warning) - Root cause:getPlayerIpRaw()usesplayer.getAddress().getAddress().getHostAddress(). Behind a proxy, this resolves to the proxy's own address (commonly127.0.0.1or a private RFC-1918 range), not the connecting client's real IP. IP-based lockout mirroring andip_limittracking silently group all players under the same key. - Fix (partial): Full proxy-IP forwarding requires server-side configuration outside the plugin's scope (Paper IP forwarding,BungeeGuard, etc.). This release adds a debug-mode warning logged whendebug: trueand the resolved address is a loopback or private-range address, so administrators are alerted during setup. The warning is suppressed in production (debug: false) to avoid console spam on legitimate LAN servers. ---
| Key | Files | Change | |-----|-------|--------| |Language File Changes
auth_session_active|en.yml,vn.yml,ru.yml| Added — shown when an existing valid session is reused | All other keys are unchanged and backwards-compatible. ---
- Removed four now-unused imports fromInternal / Code Quality
OPShield.java:LocalDateTime,ZoneId,Instant,DateTimeFormatter(timestamp moved toAuditLogger),Component,NamedTextColor,PlainTextComponentSerializer(shadow-ban message fix). -LockoutManager.cleanupExpired()signature updated (internal only). - Added inlineFIX[…]comments at every corrected site for audit trail. ---
| Item | Status | |------|--------| |Compatibility
config.yml(v1.9.0) |No changes required | |
data.yml(v1.9.0) |No changes required | | Language files (v1.9.0) |
auth_session_activekey added; old files work but players will see[Missing Message: auth_session_active]until updated | | Paper API |1.21+ (unchanged) | ---
1.9.0OPShield 1.9.0May 15, 2026 [1.9.0] - 2026-05-15 — Async Auth, Session Timeout & Security Hardening
#### FIX [Critical] — PBKDF2 verification was running on the main server thread - Root cause:Bug Fixes
handlePrivilegeCommand()calledPasswordHasher.verify()synchronously duringonCommand(), which executes on the main thread. With 120,000 PBKDF2 iterations, each failed attempt caused a measurable TPS drop (~50–200 ms). Under coordinated brute-force from multiple clients this could stall the server tick loop. - Fix: Password verification is now dispatched viaBukkit.getScheduler().runTaskAsynchronously(). The result is delivered back to the main thread viarunTask()for all Bukkit API side-effects. Zero blocking on the main thread. - New status field:/opshield statusnow shows pending verification count (0/4). #### FIX [Critical] — No session timeout after successful authentication - Root cause: After a correct password was accepted, no state was stored. Every subsequent/opor/deoprequired re-entering the password. In practice admins worked around this by keeping a note of the password nearby, which is a security anti-pattern. - Fix: AConcurrentHashMap<UUID, Long>(authenticatedSessions) tracks expiry epoch-ms per player. After the first successful auth within a session window, password entry is skipped. - Config key:security.session_timeout_minutes(default30; set0to disable). - Session lifecycle: granted on successful auth → cleared on player quit (PlayerQuitEvent) → cleared on manual/opshield unlock→ purged by cleanup task. #### FIX [High] — No global rate limit on PBKDF2 operations - Root cause: A coordinated attack with many accounts spamming/op <name> <guess>queued unbounded async hashing tasks, saturating CPU cores. - Fix:AtomicInteger globalAuthCountcaps server-wide concurrent PBKDF2 operations atMAX_CONCURRENT_AUTH = 4. Requests over the cap receiveauth_server_busyand are dropped without queuing. - Per-sender dedup:Map<String, AtomicInteger> authPendingprevents the same player from queuing more than one verification at a time. Excess requests receiveauth_pending. #### FIX [High] —cleanupRuntimeState()ran on the main thread - Root cause:Bukkit.getScheduler().runTaskTimer()was used instead ofrunTaskTimerAsynchronously(). The cleanup method holds synchronized blocks onLockoutRecordobjects; under load this briefly stalled the tick. - Fix: Scheduler call changed torunTaskTimerAsynchronously(). All collections involved areConcurrentHashMap/ConcurrentLinkedQueue— safe for async access. #### FIX [Medium] — Shutdown race condition between async periodic flush andonDisablesync flush - Root cause:onDisable()calledflushPersistentDataSync()while the 200-tick async flush task could be mid-execution. AlthoughcompareAndSetprevented double saves, the two threads could both have a reference todataFileand compete on the sameYamlConfigurationserialisation. - Fix:onDisable()now cancels both async task IDs (periodicFlushTaskId,cleanupTaskId) before callingflushPersistentDataSync(), ensuring only one flush can run at shutdown. #### FIX [Medium] —ShadowBanManagerusedHashMapfor command→message mapping - Root cause:HashMaphas undefined iteration order. Commands likedeop op,ban-ip, orgive opcould match the wrong message key depending on JVM run, leading to non-deterministic fake responses. - Fix: Replaced withLinkedHashMappreserving explicit insertion order. More specific and longer tokens (e.g."inventory","teleport","deop") are declared before shorter substrings ("clear","tp","op") so prefix-matching is always predictable. #### FIX [Low] —config-versionfield was written but never read - Root cause:config-version: 2was introduced in v1.8.0 butreloadConfiguration()never checked it. There was no warning when admins ran an old config against a new build. - Fix:reloadConfiguration()now comparesconfig-versionagainstEXPECTED_CONFIG_VERSION = 3. It logs a warning if the file is outdated (new options missing → defaults used) or from a newer build (unknown keys may be ignored). The version is bumped in-place so the warning fires only once. #### FIX [Low] —PlayerQuitEventnot handled; stale session UUIDs accumulated - Root cause:authenticatedSessionswas new in this version, but without a quit handler a UUID could remain in the map until the cleanup task ran, creating a window where a reconnecting player could bypass auth. - Fix:@EventHandler onPlayerQuit()immediately removes the UUID fromauthenticatedSessionson disconnect. ---
#### Authenticated Session Tokens - First successfulNew Features
/opor/deopauthentication grants a timed session. - Subsequent privilege commands within the window skip password entry. - Session duration:security.session_timeout_minutes(default30;0= always require password). - Sessions are cleared on disconnect, manual unlock, or config reload. - New language keys:auth_session_granted,auth_pending,auth_server_busy. #### Async PBKDF2 Authentication Pipeline - All password verifications now run asynchronously. - Bukkit API side-effects (setOp, messages, broadcasts) still execute on the main thread via callback. - ExtractedcompletePrivilegeCommand()to consolidate post-auth logic (hash upgrade, session grant, lockout clear, setOp, broadcast). #### Enhanced/opshield statusOutput Two new status lines:
---Active auth sessions: 2 Pending PBKDF2 verifications: 0/4 Session timeout: 30 min
| Key | Change | Default | |-----|--------|---------| |Configuration Changes
config-version| Bumped2→3|3| |security.session_timeout_minutes| NEW — auth session TTL in minutes;0= disabled |30| No existing keys were removed or renamed. Oldconfig-version: 2files are read normally with a one-time console warning. ---
Three new message keys added to all language files (Language Files
en,vn,ru): | Key | Purpose | |-----|---------| |auth_pending| Sender already has a PBKDF2 verification in flight | |auth_server_busy| Server-wide auth cap reached | |auth_session_granted| Confirmation message shown after session is created | ---
| Metric | v1.8.0 | v1.9.0 | |--------|--------|--------| | PBKDF2 on main thread |Code Quality Metrics
yes |
no (async) | | Session timeout |
|
| | Global auth rate limit |
|
(cap=4) | | Per-sender auth dedup |
|
| | Cleanup on main thread |
yes |
no (async) | | Shutdown race condition |
present |
fixed | | ShadowBan match order | non-deterministic | deterministic | |
config-versionenforced ||
| |
PlayerQuitEventhandled ||
| ---
1. No breaking changes — existingMigration Notes from v1.8.0
config.ymlanddata.ymlare fully compatible. 2. New config key — addsecurity.session_timeout_minutes: 30(or let the default apply on first reload). 3. config-version is auto-bumped from2→3on first reload; a one-time warning will appear in console. 4. Permission setup is unchanged from v1.8.0.
- ExtractPlanned for v1.10.0
AutoPunishmentManager— move all ban/kick/firewall logic out ofOPShield.java- ExtractIpLimitManager— move IP tracking and flagging - ExtractCommandRestrictionManager- Unit tests forLockoutManagerandShadowBanManager- Consider Argon2id as an optional stronger hashing algorithm ---1.8.0OPShield 1.8.0Apr 28, 2026 [1.8.0] - 2026-04-28 — Manager Refactor, Security Hardening & Quality Improvements
#### CRITICAL — Permission Default Changed - `opshield.admin` default changed from `op` → `false` (breaking if you relied on implicit OP grants) - Previously any player with OP status automatically received full OPShield admin rights - Now all permissions must be explicitly granted via a permission plugin (e.g. LuckPerms) - Migration: addSecurity Fixes
opshield.adminto your OP group in your permission plugin - All child permissions (opshield.reload,opshield.unlock,opshield.op,opshield.deop) default changed fromop→falsefor the same reason - Addedopshield.*wildcard permission for convenience
#### LockoutManager — Full Refactor - IntroducedArchitecture Improvements
LockoutRecordinner class consolidating 5 separateConcurrentHashMaps (failedAttempts, lockoutTimestamps, lockoutCount, lastLockoutAt) into a single per-key object - Decay logic moved entirely intoLockoutManager.recordFailure()— no longer split between main class and manager - AddedmirrorLockout()for IP-mirrored lockouts (called by OPShield whentrack_ip=true) - AddedexportSnapshot()/importSnapshot()for clean persistence without raw map access - Backwards-compatible persistence: v1.8.0 reads legacy v1.7.0 data format and migrates automatically -LockoutManageris now the single source of truth for all lockout state #### ShadowBanManager — Full Refactor - Shadow-ban levels now owned by `ShadowBanManager` (previously a rawConcurrentHashMapinOPShield.java) -getFakeMessage()is no longer static; it accepts aMessageProviderfunctional interface so messages come from language files, not hard-coded strings - AddedshouldEscalate(key, threshold)method — clearly separates the "should I punish?" decision from execution - AddedexportLevels()/importLevels()for persistence - Extended command keyword → message-key mapping: now coversop,deop,kick,stop,reload,pardon#### OPShield.java — Reduced God Class Burden - Replaced 5 raw state maps with delegation toLockoutManager- ReplacedplayerShadowBanLevelmap with delegation toShadowBanManager- Added/opshield statuscommand for runtime diagnostics (shows active levels, flagged IPs, queue sizes) - AddeddebugLog()helper — controlled bydebug: falseconfig key; never exposes sensitive info in production
- `debug` mode (New Features
debug: falsein config.yml) — enables verbose internal logging for troubleshooting without recompiling - `/opshield status` — new sub-command withopshield.statuspermission; reports shadow-ban level count, flagged IPs, sensitive-history windows, auto-punishment state - `security.password.auto_upgrade_legacy_hash: true` — automatically re-hashes a legacy SHA-256 password to PBKDF2 the next time the correct password is provided; hash is saved to config.yml with no manual action required - Audit queue capacity (audit.max_queue_size: 10000) — prevents unbounded memory growth if disk writes fail; oldest entries dropped with a console warning (rate-limited to once per flush cycle) - Audit JSON format (audit.format: json) — emits one machine-readable JSON object per line for log aggregator ingestion;plainformat unchanged for backwards compatibility
- `ShadowBanManager.getFakeActionMessage()` was never called — v1.7.0 added it but the main class still used hard-coded logic. Now the manager is the sole source of fake messages - `LockoutManager.ipLimitMap` was unused —Bug Fixes
recordIpConnection()was called but the data was never read. Removed; IP limit tracking remains in OPShield.java pendingIpLimitManagerextraction - `PasswordHasher.upgradeHashIfNeeded()` (NEW) —isLegacyHash()existed in 1.7.0 but there was no code path to actually upgrade the stored hash. Now the main class callsupgradeHashIfNeeded()after each successful login whenauto_upgrade_legacy_hash: true- `HASH_FORMAT_VERSION` constant (NEW) — the string"pbkdf2"was scattered as a magic literal acrossPasswordHasher; centralised to a named constant
- `maven-compiler-plugin 3.13.0` added with explicitBuild Improvements
<release>21</release>and<parameters>flag - `maven-shade-plugin 3.6.0` added (no relocations yet, but scaffold is ready for future bundled deps) - `maven-surefire-plugin 3.2.5` added with JUnit 5 + Mockito test dependencies for unit testing managers - Centralised version properties —java.version,paper.version, and plugin versions now all defined in<properties>for consistency
- AddedConfiguration
config-version: 2— allows future automatic migration detection - Addeddebug: false— verbose diagnostic logging toggle - Addedsecurity.password.auto_upgrade_legacy_hash: true- Addedaudit.max_queue_size: 10000- Addedaudit.format: plain- Addedshadow_ban.auto_punish_leveldefault raised from3→5- Added inline "Recommended values by server size" comments toconfig.yml
- Added 7 new shadow-fake message keys:Language Files
shadow_fake_op,shadow_fake_deop,shadow_fake_kick,shadow_fake_pardon,shadow_fake_stop,shadow_fake_reload(all three languages) - Fixed inconsistent Vietnamese translations invn.yml- All three language files now use natural-language fake messages that better blend in with real server output
| Metric | v1.7.0 | v1.8.0 | |---|---|---| | Raw state maps in OPShield.java | 7 | 3 | | Manager classes | 2 (stub) | 2 (fully active) | | Permissions with insecure defaultCode Quality Metrics
op| 6 | 0 | | Hard-coded fake messages | 8 | 0 | | Unused manager methods | 2 | 0 | |config-version||
| | Debug mode |
|
| | Audit queue cap |
|
| | JSON audit format |
|
|
1. Permission plugin setup required — addMigration Notes
opshield.adminto your OP group (see CRITICAL note above) 2. data.yml is auto-migrated from v1.7.0 format on first boot — no manual action needed 3. All configuration keys are backwards-compatible; new keys use sensible defaults 4. Oldlockout_timestamps/failed_attempts/lockout_count/last_lockout_atsections indata.ymlare read on upgrade and merged intolockout_records; old sections are replaced on next save
- ExtractPlanned for v1.9.0
AutoPunishmentManager— move all ban/kick/firewall logic out ofOPShield.java- ExtractIpLimitManager— move IP tracking and flagging - ExtractCommandRestrictionManager— movematchesConfiguredCommandlogic - Add unit tests forLockoutManagerandShadowBanManager- Consider Argon2id as an optional stronger hashing algorithm ---1.7.0OPShield 1.7.0Apr 24, 2026 [1.7.0] - 2026-04-24 — Architecture Improvements & God Class Refactoring
#### Architecture Refactoring - LockoutManager (NEW) — Extracted lockout logic from main class - Centralized player/IP lockout state management - Clean public API for lockout operations - Expired lockout cleanup methods - ShadowBanManager (NEW) — Extracted shadow ban logic - Shadow ban state tracking - Fake action message generation - Duration management with expiry cleanup - Improved message consistency #### Password Security - PasswordHasher improvements — Enhanced password handling - PBKDF2 iteration count configurable at runtime - Legacy SHA-256 detection withImprovements
isLegacyHash()method - Iteration count validation (10,000 - 1,000,000 range) - Better separation of hash versioning concerns #### Code Organization - Createdmanager/package for extracting business logic - Reduced OPShield.java God Class burden - Better separation of concerns - Improved testability of individual components #### Language Files - Fixed grammar inconsistencies (e.g., "1 player" vs "1 players") - Improved fake action message clarity - Better error message wording
#### Metrics - Before: 1,172 LOC in single class (God Class) - After: OPShield.java reduced + 2 new manager classes - Managers Created: 2 (LockoutManager, ShadowBanManager) - Lines Extracted: ~300+ from main class #### Quality Improvements -Code Quality
Reduced cyclomatic complexity in main class -
Improved code organization -
Better separation of concerns -
More testable components -
Easier to extend for future features
For existing servers: 1. No database migration needed 2. All configuration stays the same 3. No command changes 4. Direct drop-in JAR replacementMigration Notes
Recommended further refactoring: - ExtractFuture Work (v1.8.0+)
AutoPunishmentManagerfor ban/kick logic - ExtractIpLimitManagerfor IP tracking - ExtractCommandRestrictionManagerfor command validation - Create interface-based services for better testability - Add unit tests for new manager classes ---1.6.0OPShield 1.6.0Apr 21, 2026 [1.6.0] — 2026-04-21
Bug fixes
#### High severity - `AuditLogger` switched from `FileWriter` to NIO `Files.write()` —FileWriterused the JVM platform default charset, which could produce garbled or truncated log entries on servers whose OS locale is not UTF-8. All writes now usejava.nio.file.Files.write()with an explicitStandardCharsets.UTF_8argument andStandardOpenOption.APPEND. - `ensureFile()` is no longer called on every flush tick — the previous implementation re-checked (and conditionally re-created) the log file and its parent directory on every async flush, even when neither had changed. AnAtomicBoolean fileReadyflag now gates the check so it runs at most once per file lifetime. The flag is cleared after rotation so the next write correctly re-creates the log file. - Failed audit writes now re-queue entries instead of silently discarding them — if a flush attempt throwsIOException, the affected lines are returned to the front of the queue and retried up toMAX_WRITE_RETRIES(2) times. On final failure aSEVEREconsole error is printed and the lines are re-queued so they are not permanently lost. #### Medium severity - Legacy SHA-256 password hash triggers a console warning on startup — ifop_password_hashinconfig.ymlcontains an old SHA-256 value (generated by OPShield < 1.4.0), the server console now displays a clear warning advising the admin to reset the password so it is upgraded to PBKDF2 storage. The plugin continues to accept the legacy hash for authentication; no data is lost. #### Low severity - **Magic string"unknown"for unresolvable player IPs replaced with named constantUNKNOWN_IP** — eliminates the class of silent typo bugs where inconsistent string literals caused an IP to be handled as a real address in some code paths but skipped correctly in others. - Grammar correction in English shadow-ban fake messages —shadow_fake_clearincorrectly read"Cleared the inventory of 1 players". Corrected to `"Cleared the inventory of 1 player". Related entity messages (shadow_fake_kill,shadow_fake_tp`) also updated to use the singular form"entity"for consistency. - `folia-supported: false` added to `plugin.yml` — OPShield uses the Bukkit task scheduler and is not compatible with Folia. The flag prevents Folia auto-detection from incorrectly classifying the plugin as Folia-safe and loading it on an incompatible runtime. - `auto_punish_firewall_fail` message key added to all language files — previously the firewall punishment path had no dedicated message for the case where the script is skipped (unsafe exec disabled, blank script, or unknown IP). All three language files (en.yml,vn.yml,ru.yml) now include the key.Improvements
- PBKDF2 iteration count is now configurable viasecurity.password.pbkdf2_iterations(default120000, range10000–1000000). Increasing the value raises brute-force resistance at the cost of slightly slower verification on each/opor/deopattempt. Existing stored hashes are unaffected — they carry their own iteration count. - `firewall_script` config entry now includes OS-specific examples — the config comment now shows both a Linuxiptablesexample and a Windowsnetshexample so admins know the expected format without having to consult external documentation.New config keys
| Key | Default | Description | |-----|---------|-------------| |security.password.pbkdf2_iterations|120000| PBKDF2 iteration count for new password hashes (10 000–1 000 000) |New language keys (all files)
| Key | Description | |-----|-------------| |auto_punish_firewall_fail| Shown when firewall punishment is skipped and player is kicked instead | ---1.5.0OPShield 1.5.0Apr 20, 2026 [1.5.0] — 2026-04-19
Bug fixes
#### Medium severity - `sensitiveCommandHistory` now persisted to `data.yml` — the auto-punishment rolling window survived previously only in memory, allowing players to bypass the threshold by timing restarts or crashes. Timestamps are now written on every dirty flush and restored on startup; stale entries outside the configured window are discarded automatically on load. - Shadow-ban level escalation is now enforced —playerShadowBanLevelwas incremented and stored but never acted upon. A new config keyshadow_ban.auto_punish_level(default3) defines the threshold at which the level triggers realauto_punishmentand then resets. Requiresauto_punishment.enabled: true. - Firewall script no longer blocks the main thread —executeFirewallBlockpreviously calledRuntime.getRuntime().exec()synchronously, which could freeze the server if the script was slow. It now runs asynchronously viaProcessBuilderwith a configurable hard timeout (auto_punishment.firewall_timeout_seconds, default10). The player is kicked immediately on the main thread; the OS script executes in the background. #### Low severity - `unlockIdentifier()` now clears `sensitiveCommandHistory` — previously,/opshield unlockcleared all other tracking maps but leftsensitiveCommandHistoryintact, causing inconsistent state after a manual unlock. - `getMsgPlain()` replaced fragile color-strip logic — manualreplace('&X', "")calls missed several color codes and decorators. Now uses Adventure'sPlainTextComponentSerializerfor correct, future-proof plain-text extraction. - Multi-file audit log rotation —AuditLoggerpreviously kept only one backup file (audit.log.1), permanently overwriting it on every rotation. Rotation now shifts files:audit.log.1→audit.log.2→ … →audit.log.N. Controlled byaudit.log_retention(default3). - Config validation for `auto_punishment.command` — an unrecognized mode with nocustom_commandset now prints a clear console warning on load and reload instead of silently falling back to a potentially unexpected behaviour.New config keys
| Key | Default | Description | |-----|---------|-------------| |shadow_ban.auto_punish_level|3| Shadow-ban level threshold that triggers auto-punishment | |audit.log_retention|3| Number of rotated audit log backup files to keep | |auto_punishment.firewall_timeout_seconds|10| Max seconds before a hung firewall script is force-killed | ---1.4.0OPShield 1.4.0Apr 14, 2026 [1.4.0] — 2026-04-14
Security hardening
- migrated password storage toop_password_hashso plaintext is no longer kept in config after migration - added PBKDF2 password hashing for new stored credentials - preserved backward compatibility for older SHA-256 hashes during migration - made firewall execution explicitly unsafe and opt-in only viaallow_unsafe_firewall_exec- disabled auto-punishment by default to reduce accidental false positives on fresh installsLogic fixes
- fixed IP-limit detection so it counts unique accounts inside a real rolling time window - changed OP whitelist enforcement to apply to/oponly - added lockout count decay after a configurable cooling-off period - cleaned expired lockouts and expired IP flags automatically on a schedule - localized shadow-ban fake success messages instead of hardcoding English strings in JavaPerformance and maintainability
- replaced repeated async save spawns with a debounced persistent save loop - replaced synchronous audit file writes with queued async flushes - added basic audit log rotation - cachedCommandMapreflection result instead of resolving it on every blocked command - split hashing and audit logging into dedicated helper classesPermissions and command handling
- addedopshield.op- addedopshield.deop- addedopshield.admin- addedopshield.bypass- kept/opshield reloadand/opshield unlock <player|ip>as admin management commandsConfig changes
- addedop_password_hash- retainedop_passwordonly as a legacy migration input - addedbroadcast_on_privilege_change- addedaudit.*- addedsecurity.lockout.track_ip- addedsecurity.lockout.count_decay_hours- addedauto_punishment.window_seconds- addedauto_punishment.custom_command- addedauto_punishment.allow_unsafe_firewall_exec- addedip_limit.auto_punish- addedip_limit.flag_duration_minutes1.3.0OPShield 1.3.0Mar 28, 2026 [1.3.0] — 2026-03-28
#### Auto-Punishment (Automatic Punishment) - Automatically ban-ip or execute punishment command when players attempt sensitive commands too many times - Config:New Security Features
auto_punishment.threshold- maximum allowed attempts - Supports multiple modes:ban-ip,ban,kick,firewall, or custom command - Firewall script: Execute system commands (iptables/ufw) to block IP at network level - Configurable sensitive commands list:sensitive_commands#### IP-Limit (Multi-account Detection) - Track number of accounts connecting from the same IP - Auto-flag and punish if exceedingmax_accountswithin time window - Default: 3 accounts / 5 minutes - Integrates with Auto-Punishment to automatically block suspicious IPs #### Shadow Ban / Fake Success (Deceptive) - Instead of showing "no permission", display fake success messages - Attackers think they succeeded but nothing actually happens - Buys time for admin to investigate and handle attackers - Fake messages for commands: clear, kill, ban, gamemode, tp, effect, give #### Anti-Spam Console Log - Blocked commands no longer appear in console log - Only logged to audit.log to prevent server console spam
- Auto-punishment exempt for OP and whitelist-admin - IP tracking with automatic time window cleanup - Full audit logging for all auto-punishment actions ---Security Improvements
1.2.1OPShield 1.2.1Mar 16, 2026 [1.2.1] — 2026-03-16
- OP Command Restriction — Added ability to restrict specific commands even for OPs. This helps prevent admin abuse and limits potential damage from backdoors. - New config options:New Features
restrict_op_commands(boolean) andblocked_op_commands(list). - Console remains exempt from these restrictions for emergency recovery.1.2.0OPShield 1.2.0Mar 15, 2026 [1.2.0] — 2026-03-15
#### BUG-1 — DeprecatedBug Fixes (7 fixes)
ChatColorAPI replaced with AdventureLegacyComponentSerializer- Root cause:getMsg()usedChatColor.translateAlternateColorCodes('&', msg), which is deprecated in Paper 1.21 and produces build warnings. - Fix: Replaced withLegacyComponentSerializer.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: falsehad 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 triggeredsavePersistentData()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 deletedata.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: IntroducedlockoutCount(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 onsender 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 —CommandMapreflection failure was fully silent - Root cause: IfgetCommandMap()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: Replacedignoredwith 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 ---1.1.1OPShield 1.1.1Mar 11, 2026 [1.1.1] — 2026-03-11
Fixed
- Alias bypass (EssentialsX and other plugins): Non-OPs could use blocked commands via aliases (/gm,/gms,/tpa,/tphere, etc.) because the plugin compared only the typed text. Commands are now resolved to their canonical name via Bukkit'sCommandMapbefore checking the block list, so aliases are caught automatically without needing to enumerate them in config. - Non-OP players could not use `/op <player> <password>`:"op"was listed inblocked_commands, causingPlayerCommandPreprocessEventto cancel the command before it ever reachedonCommand. Password-based OP now works correctly for non-OP players. - Password exposed in `logs/latest.log`:/opand/deopevents were not cancelled before Bukkit logged them, so the password appeared in plain text in console output and log files. The event is now cancelled immediately and dispatched directly toonCommandinternally. - `getRemainingLockoutTime` showed inflated minutes: Integer division added an extra minute at every boundary (e.g. 5 seconds remaining showed "1 minute"). Fixed to ceiling division. ---1.0.4OPShield 1.0.4Mar 7, 2026 fixed1.0.3OPShield 1.0.3Mar 7, 2026
All notable changes to the OPShield plugin in this version are documented below. ---Changelog - OPShield v1.0.3
[1.0.3] — 2026-03-07
Full Localization:* The plugin now supports multiple languages. All messages have been moved fromMulti-Language Support
config.ymlto separate language files. Included Languages:* English* (en.yml) - Default. Vietnamese* (vn.yml). Russian* (ru.yml). Dynamic Language Switching:* Change the language inconfig.ymland use/opshield reloadto switch instantly without a server restart. Auto-Resource Generation:* The plugin automatically creates default language files in the plugin folder if they are missing.
* AddedConfiguration Changes
languageoption toconfig.yml. * Removed hardcoded messages fromconfig.ymlto minimize clutter and improve maintainability.
* Built a flexible localization manager to handle message loading and caching. * Ensured consistency across color codes and placeholders in all translations. ---Internal Improvements
1.0.1OPShield 1.0.1Mar 7, 2026 Changelog - OPShield v1.0.1
- All notable changes to the OPShield plugin in this version are documented below. - [1.0.1] - 2026-03-07Critical Security Fixes Inverted Logic Fix: Resolved a critical bug where restrict_admin_commands: true would block OPs instead of protecting them. Existing OPs are now correctly exempted from command restrictions. SHA-256 Password Hashing: Upgraded password storage and comparison from plain-text to SHA-256 hashing. Passwords are now securely hashed in memory to prevent exposure. Secure Default Password: Added an automatic check for the default password (secure123). The plugin now generates a strong 16-character random password if the default is detected. -
New Security Features Console Password Requirement: Added require_password_from_console (Default: false). When enabled, the console must provide the secret password to use /op or /deop. /deop Protection: Extended password authentication to the /deop command, preventing unauthorized removal of admin rights. Wildcard/Prefix Blocking: Introduced blocked_command_prefixes. Admins can now block entire namespaces (e.g., essentials:, minecraft:) to prevent bypasses via aliases. Audit Logging: Implemented a dedicated audit log system. All authentication attempts and blocked command actions are now logged with timestamps in plugins/OPShield/audit.log. -
Configuration Changes Added require_password_from_console setting. - Added blocked_command_prefixes list. - Updated config.yml comments for better clarity on security features. Added localization keys for de-op operations. -
Internal Improvements - Registered /op and /deop as internal overrides in plugin.yml for more reliable interception. Normalized command processing to be more robust against casing and prefix bypasses.
1.0.0OPShield 1.0.0Mar 7, 2026
Similar projects
Plugin →Let Me Despawn
Improves performance by tweaking mob despawn rules. Say bye to pesky unintentional persistent mobs.
25MSueta
JourneyMap
Real-time map used for mapping in-game or your browser as you explore.
14.7MSueta
WorldEdit
A Minecraft Map Editor... that runs in-game! With selections, schematics, copy and paste, brushes, and scripting. Use it in creative, or use it temporarily in survival.
11.6MSueta
Emotecraft
Create your own emotes in Minecraft.
7.5MSueta
Simple Tpa
A simple tpa plugin which sends requests
5.4MSueta
Common Network
Forge and Fabric Unified Networking Solution
3.9MSueta