
OPShield
Protects your server from OP/admin abuse with console-only OP (via password) and optional admin command restriction.
191
1
Список изменений
[1.9.0] - 2026-05-15 — Async Auth, Session Timeout & Security Hardening
🐛 Bug Fixes
FIX [Critical] — PBKDF2 verification was running on the main server thread
- Root cause:
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 via
Bukkit.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: A
ConcurrentHashMap<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 to
runTaskTimerAsynchronously(). All collections involved areConcurrentHashMap/ConcurrentLinkedQueue— safe for async access.
FIX [Medium] — Shutdown race condition between async periodic flush and onDisable sync 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] — ShadowBanManager used HashMap for 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 with
LinkedHashMappreserving 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-version field 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] — PlayerQuitEvent not 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.
✨ New Features
Authenticated Session Tokens
- First successful
/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.
- Extracted
completePrivilegeCommand()to consolidate post-auth logic (hash upgrade, session grant, lockout clear, setOp, broadcast).
Enhanced /opshield status Output
Two new status lines:
Active auth sessions: 2
Pending PBKDF2 verifications: 0/4
Session timeout: 30 min
🔧 Configuration Changes
| Key | Change | Default |
|---|---|---|
config-version | Bumped 2 → 3 | 3 |
security.session_timeout_minutes | NEW — auth session TTL in minutes; 0 = disabled | 30 |
No existing keys were removed or renamed. Old config-version: 2 files are read normally with a one-time console warning.
🌍 Language Files
Three new message keys added to all 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 |
📊 Code Quality Metrics
| Metric | v1.8.0 | v1.9.0 |
|---|---|---|
| PBKDF2 on main thread | ✅ 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-version enforced | ❌ | ✅ |
PlayerQuitEvent handled | ❌ | ✅ |
📝 Migration Notes from v1.8.0
- No breaking changes — existing
config.ymlanddata.ymlare fully compatible. - New config key — add
security.session_timeout_minutes: 30(or let the default apply on first reload). - config-version is auto-bumped from
2→3on first reload; a one-time warning will appear in console. - Permission setup is unchanged from v1.8.0.
🔮 Planned for v1.10.0
- Extract
AutoPunishmentManager— move all ban/kick/firewall logic out ofOPShield.java - Extract
IpLimitManager— move IP tracking and flagging - Extract
CommandRestrictionManager - Unit tests for
LockoutManagerandShadowBanManager - Consider Argon2id as an optional stronger hashing algorithm
Метаданные
Канал релиза
Release
Номер версии
1.9.0
Загрузчики
BukkitPaperPurpurSpigot
Версии игры
1.21–1.21.11
Загрузок
9
Дата публикации
1 мес. назад
