diff --git a/README.md b/README.md
index ebc2f21..c064429 100644
--- a/README.md
+++ b/README.md
@@ -77,7 +77,7 @@

- 30+
+ 85+
安全模块
|
@@ -148,7 +148,7 @@
|
🛡️ 全栈纵深防御
- 从 MC 协议层 → 应用 WAF → 系统防火墙 → Cloudflare 边缘节点。30+ 安全模块覆盖 L3-L7,零信任架构、蜜罐诱捕一应俱全。
+ 从 MC 协议层 → 应用 WAF → 系统防火墙 → Cloudflare 边缘节点。85+ 安全模块覆盖 L3-L7,零信任架构、蜜罐诱捕一应俱全。
|
@@ -261,7 +261,7 @@ cell.pressure = cell.pressure × 0.55 + pulse.heat × 0.75
│ │ Process │ │ DDoS/IDS │ │ DeepSeek │ │
│ │ Resource │ │ WAF/Honey│ │ Anomaly │ │
│ │ Connect │ │ ZeroTrust│ │ Predict │ │
- │ │ Log │ │ 30+ svc │ │ Adaptive │ │
+ │ │ Log │ │ 85+ svc │ │ Adaptive │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └─────────┬───┴─────────────┘ │
@@ -810,7 +810,7 @@ serverguard
### 安全防护层 (`com.aluer.security`)
-这是系统中规模最大的包,包含 65+ 个安全服务(含 25 个超进化 v3.2 新增模块):
+这是系统中规模最大的包,包含 85+ 个安全服务(含 31 个超进化 v3.1 / v3.2 / v3.3 新增模块):
| 类别 | 类 | 功能 |
|------|-----|------|
@@ -885,6 +885,12 @@ serverguard
| | `AntiDupeDetectionService` | 物品复制检测:堆叠溢出、高价值物品暴涨、9种已知复制方法监测 |
| | `CrashExploitProtectionService` | 崩溃漏洞防护:超大包/负长度/NBT嵌套/书与笔/Sign炸弹/Tile Entity洪水 |
| | `LagMachineDetectionService` | 卡服机检测:Observer链/TNT堆叠/红石密度/实体刷怪/掉落方块洪流 |
+| **v3.3 扩展** | `GeoBlockService` | 地理IP封锁:国家/地区黑白名单,高风险Minecraft攻击来源国拦截 |
+| | `PlayerSessionValidationService` | 玩家会话验证:UUID格式/离线模式/正版检测/会话重放/多账号识别 |
+| | `PluginVerificationService` | 插件完整性校验:SHA-256哈希对比、恶意插件名检测、JAR结构检查 |
+| | `ConnectionThrottleService` | 连接速率限制:多时间窗口IP追踪、递增延迟(2s/5s/15s/30s)、并发上限 |
+| | `BackupIntegrityService` | 备份完整性验证:SHA-256校验和、文件存在性/大小对比、6小时自动扫描 |
+| | `AntiSkinSpoofService` | 皮肤伪造检测:知名主播皮肤冒充、可疑URL、皮肤变更频率、模型类型异常 |
### 运营控制层 (`com.aluer.console`)
diff --git a/README_EN.md b/README_EN.md
index a17dbde..ad3bbb2 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -83,7 +83,7 @@

- 30+
+ 85+
Security Modules
|
@@ -154,7 +154,7 @@
|
🛡️ Full-Stack Defense
- From Minecraft protocol layer → Application WAF → OS firewall → Cloudflare edge. 30+ security modules covering L3-L7, zero-trust architecture, and honeypot deception.
+ From Minecraft protocol layer → Application WAF → OS firewall → Cloudflare edge. 85+ security modules covering L3-L7, zero-trust architecture, and honeypot deception.
|
@@ -269,7 +269,7 @@ cell.pressure = cell.pressure × 0.55 + pulse.heat × 0.75
│ │ Process │ │ DDoS/IDS │ │ DeepSeek │ │
│ │ Resource │ │ WAF/Honey│ │ Anomaly │ │
│ │ Connect │ │ ZeroTrust│ │ Predict │ │
- │ │ Log │ │ 30+ svc │ │ Adaptive │ │
+ │ │ Log │ │ 85+ svc │ │ Adaptive │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └─────────┬───┴─────────────┘ │
@@ -667,7 +667,7 @@ serverguard
### Security Defense Layer (`com.aluer.security`)
-The largest package in the system, containing 65+ security services (with 25 Super Evolution v3.2 modules):
+The largest package in the system, containing 85+ security services (with 31 Super Evolution v3.1 / v3.2 / v3.3 modules):
| Category | Key Classes | Description |
|----------|------------|-------------|
@@ -707,6 +707,12 @@ The largest package in the system, containing 65+ security services (with 25 Sup
| | `AntiDupeDetectionService` | Item duplication detection: stack overflow, high-value item surges, 9 known methods |
| | `CrashExploitProtectionService` | Crash exploit protection: oversized packets, NBT bombs, book/sign exploits |
| | `LagMachineDetectionService` | Lag machine detection: observer chains, TNT stacks, entity spam, falling blocks |
+| **v3.3 New Modules** | `GeoBlockService` | Geo IP blocking: country allowlist/blocklist, high-risk Minecraft attack source interception |
+| | `PlayerSessionValidationService` | Player session validation: UUID format, offline/premium detection, session replay, multi-account |
+| | `PluginVerificationService` | Plugin integrity verification: SHA-256 hash comparison, malicious plugin name detection |
+| | `ConnectionThrottleService` | Connection rate limiting: multi-window IP tracking, progressive delay (2s/5s/15s/30s) |
+| | `BackupIntegrityService` | Backup integrity verification: SHA-256 checksum, file existence/size comparison, 6h auto-scan |
+| | `AntiSkinSpoofService` | Skin spoofing detection: impersonation detection, suspicious URL checks, skin change frequency |
### Operations Control Layer (`com.aluer.console`)
diff --git a/docs/USER_MANUAL.md b/docs/USER_MANUAL.md
index cc8e03c..5b4d447 100644
--- a/docs/USER_MANUAL.md
+++ b/docs/USER_MANUAL.md
@@ -452,6 +452,17 @@ export ALUER_WEBHOOK_SLACK="https://hooks.slack.com/services/xxx/yyy/zzz"
| `/api/security/crash-exploit/status` | GET | 崩溃漏洞防护状态(超大包、NBT炸弹、书与笔攻击) |
| `/api/security/lag-machine/status` | GET | 卡服机检测状态(Observer链、TNT堆、红石密度) |
+#### v3.3 新增安全 API
+
+| 接口路径 | 方法 | 说明 |
+|----------|------|------|
+| `/api/security/geo-block/status` | GET | 地理IP封锁状态(拦截计数、黑白名单大小) |
+| `/api/security/session-validation/status` | GET | 玩家会话验证状态(UUID检测、重放攻击计数) |
+| `/api/security/plugin-verification/status` | GET | 插件完整性校验状态(已验证、失败、恶意检测) |
+| `/api/security/connection-throttle/status` | GET | 连接速率限制状态(活跃IP、已拦截、已延迟) |
+| `/api/security/backup-integrity/status` | GET | 备份完整性验证状态(通过/失败/损坏计数) |
+| `/api/security/anti-skin-spoof/status` | GET | 皮肤伪造检测状态(冒充者、可疑URL、变更频率) |
+
---
## 配置说明
@@ -517,6 +528,56 @@ backup:
backup-config: true
```
+#### 超进化安全模块开关
+
+所有超进化安全模块均可通过 `application.yml` 中的 `serverguard.security.super-evolution` 节点独立控制开关。设为 `false` 即可关闭该模块,不消耗任何性能。
+
+```yaml
+serverguard:
+ security:
+ super-evolution:
+ # === v3.1 超进化安全模块(20个)===
+ jwt-auth: true # JWT身份认证与令牌管理
+ brute-force: true # 暴力破解防护(多时间窗口检测)
+ anti-bot: true # 反机器人检测(Bot名称/加入速率/IP关联)
+ reverse-shell: true # 反向Shell检测(50+ shell模式匹配)
+ arp-spoof: true # ARP欺骗检测(MAC变更/网关伪造)
+ dns-tunnel: true # DNS隧道检测(熵值/Base32编码/可疑TLD)
+ exploit-signature: true # 漏洞签名检测(Log4Shell/SQLi/RCE等15种)
+ ssrf: true # SSRF防护(内网IP/云元数据/协议限制)
+ xxe: true # XXE防护(实体注入/Billion Laughs检测)
+ csp: true # CSP安全头强制执行(8种响应头)
+ database-firewall: true # 数据库防火墙(SQL注入/联合查询/时间盲注)
+ dlp: true # 数据防泄漏(12种敏感信息规则+自动脱敏)
+ memory-protection: true # JVM内存保护(堆/GC/内存泄漏检测)
+ process-injection: true # 进程注入检测(/proc扫描/线程异常)
+ secure-delete: true # 安全文件删除(多道覆写DoD标准)
+ forensics: true # 取证收集(进程/网络/日志快照)
+ incident-response: true # 事件响应(5种预定义响应剧本)
+ threat-hunting: true # 威胁狩猎(10种狩猎定义/5个类别)
+ compliance: true # 合规扫描(7类20+检查项)
+ anti-grief: true # 反破坏检测(方块破坏率/TNT/纵火/偷箱)
+ # === v3.2 Minecraft专属模块(5个)===
+ anti-xray: true # X-ray透视检测(钻石比/直线挖掘/暗处精准)
+ anti-fly: true # 飞行外挂检测(垂直/水平速度/悬空时间)
+ anti-dupe: true # 物品复制检测(堆叠异常/高价值暴涨/9种复制法)
+ crash-exploit: true # 崩溃漏洞防护(超大包/NBT炸弹/书与笔攻击)
+ lag-machine: true # 卡服机检测(Observer链/TNT堆/红石密度)
+ # === v3.3 新模块(6个)===
+ geo-block: true # 地理IP封锁(按国家/地区拦截)
+ session-validation: true # 玩家会话验证(UUID伪造/正版/离线检测)
+ plugin-verification: true # 插件完整性校验(Hash对比/未授权修改)
+ connection-throttle: true # 连接速率限制(IP/时间窗口/递增延迟)
+ backup-integrity: true # 备份完整性校验(SHA-256/文件计数/大小对比)
+ anti-skin-spoof: true # 皮肤伪造检测(模型数据异常/皮肤URL检测)
+```
+
+**开关说明:**
+- 所有模块默认 `true`(开启),可根据需要设为 `false` 关闭
+- 关闭模块后,对应的 API 端点仍可访问但返回空数据
+- 关闭不需要的模块可以降低 CPU/内存开销
+- 修改配置后需重启服务生效
+
---
## 预警系统
diff --git a/src/main/java/com/aluer/config/ServerGuardConfig.java b/src/main/java/com/aluer/config/ServerGuardConfig.java
index 4d5ccf8..763692b 100644
--- a/src/main/java/com/aluer/config/ServerGuardConfig.java
+++ b/src/main/java/com/aluer/config/ServerGuardConfig.java
@@ -372,6 +372,7 @@ public class ServerGuardConfig {
private KernelConfig kernel = new KernelConfig();
private TaskBusConfig taskBus = new TaskBusConfig();
private SelfHealingConfig selfHealing = new SelfHealingConfig();
+ private SuperEvolutionConfig superEvolution = new SuperEvolutionConfig();
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
@@ -411,6 +412,8 @@ public class ServerGuardConfig {
public void setTaskBus(TaskBusConfig taskBus) { this.taskBus = taskBus; }
public SelfHealingConfig getSelfHealing() { return selfHealing; }
public void setSelfHealing(SelfHealingConfig selfHealing) { this.selfHealing = selfHealing; }
+ public SuperEvolutionConfig getSuperEvolution() { return superEvolution; }
+ public void setSuperEvolution(SuperEvolutionConfig superEvolution) { this.superEvolution = superEvolution; }
}
public static class MinecraftDefenseConfig {
@@ -865,6 +868,103 @@ public class ServerGuardConfig {
public void setHandshakeTtlSeconds(int handshakeTtlSeconds) { this.handshakeTtlSeconds = handshakeTtlSeconds; }
}
+ public static class SuperEvolutionConfig {
+ private boolean jwtAuth = true;
+ private boolean bruteForce = true;
+ private boolean antiBot = true;
+ private boolean reverseShell = true;
+ private boolean arpSpoof = true;
+ private boolean dnsTunnel = true;
+ private boolean exploitSignature = true;
+ private boolean ssrf = true;
+ private boolean xxe = true;
+ private boolean csp = true;
+ private boolean databaseFirewall = true;
+ private boolean dlp = true;
+ private boolean memoryProtection = true;
+ private boolean processInjection = true;
+ private boolean secureDelete = true;
+ private boolean forensics = true;
+ private boolean incidentResponse = true;
+ private boolean threatHunting = true;
+ private boolean compliance = true;
+ private boolean antiGrief = true;
+ private boolean antiXray = true;
+ private boolean antiFly = true;
+ private boolean antiDupe = true;
+ private boolean crashExploit = true;
+ private boolean lagMachine = true;
+ private boolean geoBlock = true;
+ private boolean sessionValidation = true;
+ private boolean pluginVerification = true;
+ private boolean connectionThrottle = true;
+ private boolean backupIntegrity = true;
+ private boolean antiSkinSpoof = true;
+
+ public boolean isJwtAuth() { return jwtAuth; }
+ public void setJwtAuth(boolean jwtAuth) { this.jwtAuth = jwtAuth; }
+ public boolean isBruteForce() { return bruteForce; }
+ public void setBruteForce(boolean bruteForce) { this.bruteForce = bruteForce; }
+ public boolean isAntiBot() { return antiBot; }
+ public void setAntiBot(boolean antiBot) { this.antiBot = antiBot; }
+ public boolean isReverseShell() { return reverseShell; }
+ public void setReverseShell(boolean reverseShell) { this.reverseShell = reverseShell; }
+ public boolean isArpSpoof() { return arpSpoof; }
+ public void setArpSpoof(boolean arpSpoof) { this.arpSpoof = arpSpoof; }
+ public boolean isDnsTunnel() { return dnsTunnel; }
+ public void setDnsTunnel(boolean dnsTunnel) { this.dnsTunnel = dnsTunnel; }
+ public boolean isExploitSignature() { return exploitSignature; }
+ public void setExploitSignature(boolean exploitSignature) { this.exploitSignature = exploitSignature; }
+ public boolean isSsrf() { return ssrf; }
+ public void setSsrf(boolean ssrf) { this.ssrf = ssrf; }
+ public boolean isXxe() { return xxe; }
+ public void setXxe(boolean xxe) { this.xxe = xxe; }
+ public boolean isCsp() { return csp; }
+ public void setCsp(boolean csp) { this.csp = csp; }
+ public boolean isDatabaseFirewall() { return databaseFirewall; }
+ public void setDatabaseFirewall(boolean databaseFirewall) { this.databaseFirewall = databaseFirewall; }
+ public boolean isDlp() { return dlp; }
+ public void setDlp(boolean dlp) { this.dlp = dlp; }
+ public boolean isMemoryProtection() { return memoryProtection; }
+ public void setMemoryProtection(boolean memoryProtection) { this.memoryProtection = memoryProtection; }
+ public boolean isProcessInjection() { return processInjection; }
+ public void setProcessInjection(boolean processInjection) { this.processInjection = processInjection; }
+ public boolean isSecureDelete() { return secureDelete; }
+ public void setSecureDelete(boolean secureDelete) { this.secureDelete = secureDelete; }
+ public boolean isForensics() { return forensics; }
+ public void setForensics(boolean forensics) { this.forensics = forensics; }
+ public boolean isIncidentResponse() { return incidentResponse; }
+ public void setIncidentResponse(boolean incidentResponse) { this.incidentResponse = incidentResponse; }
+ public boolean isThreatHunting() { return threatHunting; }
+ public void setThreatHunting(boolean threatHunting) { this.threatHunting = threatHunting; }
+ public boolean isCompliance() { return compliance; }
+ public void setCompliance(boolean compliance) { this.compliance = compliance; }
+ public boolean isAntiGrief() { return antiGrief; }
+ public void setAntiGrief(boolean antiGrief) { this.antiGrief = antiGrief; }
+ public boolean isAntiXray() { return antiXray; }
+ public void setAntiXray(boolean antiXray) { this.antiXray = antiXray; }
+ public boolean isAntiFly() { return antiFly; }
+ public void setAntiFly(boolean antiFly) { this.antiFly = antiFly; }
+ public boolean isAntiDupe() { return antiDupe; }
+ public void setAntiDupe(boolean antiDupe) { this.antiDupe = antiDupe; }
+ public boolean isCrashExploit() { return crashExploit; }
+ public void setCrashExploit(boolean crashExploit) { this.crashExploit = crashExploit; }
+ public boolean isLagMachine() { return lagMachine; }
+ public void setLagMachine(boolean lagMachine) { this.lagMachine = lagMachine; }
+ public boolean isGeoBlock() { return geoBlock; }
+ public void setGeoBlock(boolean geoBlock) { this.geoBlock = geoBlock; }
+ public boolean isSessionValidation() { return sessionValidation; }
+ public void setSessionValidation(boolean sessionValidation) { this.sessionValidation = sessionValidation; }
+ public boolean isPluginVerification() { return pluginVerification; }
+ public void setPluginVerification(boolean pluginVerification) { this.pluginVerification = pluginVerification; }
+ public boolean isConnectionThrottle() { return connectionThrottle; }
+ public void setConnectionThrottle(boolean connectionThrottle) { this.connectionThrottle = connectionThrottle; }
+ public boolean isBackupIntegrity() { return backupIntegrity; }
+ public void setBackupIntegrity(boolean backupIntegrity) { this.backupIntegrity = backupIntegrity; }
+ public boolean isAntiSkinSpoof() { return antiSkinSpoof; }
+ public void setAntiSkinSpoof(boolean antiSkinSpoof) { this.antiSkinSpoof = antiSkinSpoof; }
+ }
+
public static class AnnouncementConfig {
private boolean enabled = false;
private int intervalSeconds = 300;
diff --git a/src/main/java/com/aluer/security/ARPSpoofDetectionService.java b/src/main/java/com/aluer/security/ARPSpoofDetectionService.java
index 755977e..b7f63ac 100644
--- a/src/main/java/com/aluer/security/ARPSpoofDetectionService.java
+++ b/src/main/java/com/aluer/security/ARPSpoofDetectionService.java
@@ -1,5 +1,7 @@
package com.aluer.security;
+import com.aluer.config.ServerGuardConfig;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
@@ -15,6 +17,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Service
public class ARPSpoofDetectionService {
+ private final ServerGuardConfig config;
private final Map arpCache = new ConcurrentHashMap<>();
private final Map> arpEvents = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
@@ -23,8 +26,16 @@ public class ARPSpoofDetectionService {
private static final int BASELINE_SAMPLES = 3;
public ARPSpoofDetectionService() {
- buildBaseline();
- scheduler.scheduleAtFixedRate(this::scanARPTable, 30, 60, TimeUnit.SECONDS);
+ this(new ServerGuardConfig());
+ }
+
+ @Autowired
+ public ARPSpoofDetectionService(ServerGuardConfig config) {
+ this.config = config;
+ if (config.getSecurity().getSuperEvolution().isArpSpoof()) {
+ buildBaseline();
+ scheduler.scheduleAtFixedRate(this::scanARPTable, 30, 60, TimeUnit.SECONDS);
+ }
}
private void buildBaseline() {
@@ -35,6 +46,10 @@ public class ARPSpoofDetectionService {
}
public ARPScanResult scanARPTable() {
+ if (!config.getSecurity().getSuperEvolution().isArpSpoof()) {
+ return new ARPScanResult(List.of(), arpCache.size(), totalSpoofDetections.get());
+ }
+
Map current = readARPTable();
List alerts = new ArrayList<>();
diff --git a/src/main/java/com/aluer/security/AntiBotDetectionService.java b/src/main/java/com/aluer/security/AntiBotDetectionService.java
index 39f2027..5362dbd 100644
--- a/src/main/java/com/aluer/security/AntiBotDetectionService.java
+++ b/src/main/java/com/aluer/security/AntiBotDetectionService.java
@@ -1,5 +1,7 @@
package com.aluer.security;
+import com.aluer.config.ServerGuardConfig;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.Instant;
@@ -13,6 +15,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Service
public class AntiBotDetectionService {
+ private final ServerGuardConfig config;
private final Map joinHistory = new ConcurrentHashMap<>();
private final Map> ipJoinTimeline = new ConcurrentHashMap<>();
private final Map blockedBots = new ConcurrentHashMap<>();
@@ -47,10 +50,20 @@ public class AntiBotDetectionService {
private final AtomicLong totalBotsBlocked = new AtomicLong(0);
public AntiBotDetectionService() {
+ this(new ServerGuardConfig());
+ }
+
+ @Autowired
+ public AntiBotDetectionService(ServerGuardConfig config) {
+ this.config = config;
scheduler.scheduleAtFixedRate(this::cleanupExpired, 120, 300, TimeUnit.SECONDS);
}
public BotCheckResult checkPlayerJoin(String playerName, String ip, String clientBrand) {
+ if (!config.getSecurity().getSuperEvolution().isAntiBot()) {
+ return BotCheckResult.clean();
+ }
+
if (blockedBots.containsKey(ip)) {
Instant until = blockedBots.get(ip);
if (Instant.now().isAfter(until)) {
diff --git a/src/main/java/com/aluer/security/AntiDupeDetectionService.java b/src/main/java/com/aluer/security/AntiDupeDetectionService.java
index bef8d22..977299c 100644
--- a/src/main/java/com/aluer/security/AntiDupeDetectionService.java
+++ b/src/main/java/com/aluer/security/AntiDupeDetectionService.java
@@ -1,5 +1,6 @@
package com.aluer.security;
+import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service;
import java.time.Instant;
@@ -13,6 +14,8 @@ import java.util.concurrent.atomic.AtomicLong;
@Service
public class AntiDupeDetectionService {
+ private final ServerGuardConfig config;
+
private final Map inventorySnapshots = new ConcurrentHashMap<>();
private final Map> dupeEvents = new ConcurrentHashMap<>();
private final Map flaggedPlayers = new ConcurrentHashMap<>();
@@ -48,11 +51,20 @@ public class AntiDupeDetectionService {
}
public AntiDupeDetectionService() {
+ this(new ServerGuardConfig());
+ }
+
+ public AntiDupeDetectionService(ServerGuardConfig config) {
+ this.config = config;
scheduler.scheduleAtFixedRate(this::cleanupOldData, 300, 600, TimeUnit.SECONDS);
}
public DupeCheckResult checkInventoryChange(String playerName, String itemType, int newCount, int oldCount,
String containerType, int x, int y, int z) {
+ if (!config.getSecurity().getSuperEvolution().isAntiDupe()) {
+ return DupeCheckResult.clean();
+ }
+
if (flaggedPlayers.containsKey(playerName)) {
if (Instant.now().isAfter(flaggedPlayers.get(playerName))) {
flaggedPlayers.remove(playerName);
diff --git a/src/main/java/com/aluer/security/AntiFlyDetectionService.java b/src/main/java/com/aluer/security/AntiFlyDetectionService.java
index 0d5ab15..4e6bef6 100644
--- a/src/main/java/com/aluer/security/AntiFlyDetectionService.java
+++ b/src/main/java/com/aluer/security/AntiFlyDetectionService.java
@@ -1,5 +1,6 @@
package com.aluer.security;
+import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service;
import java.time.Instant;
@@ -13,6 +14,8 @@ import java.util.concurrent.atomic.AtomicLong;
@Service
public class AntiFlyDetectionService {
+ private final ServerGuardConfig config;
+
private final Map movementData = new ConcurrentHashMap<>();
private final Map flaggedPlayers = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
@@ -29,11 +32,20 @@ public class AntiFlyDetectionService {
private static final long FLAG_DURATION_SECONDS = 3600;
public AntiFlyDetectionService() {
+ this(new ServerGuardConfig());
+ }
+
+ public AntiFlyDetectionService(ServerGuardConfig config) {
+ this.config = config;
scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS);
}
public FlyCheckResult checkMovement(String playerName, double fromX, double fromY, double fromZ,
double toX, double toY, double toZ, boolean onGround, String gameMode) {
+ if (!config.getSecurity().getSuperEvolution().isAntiFly()) {
+ return FlyCheckResult.clean();
+ }
+
if (flaggedPlayers.containsKey(playerName)) {
if (Instant.now().isAfter(flaggedPlayers.get(playerName))) {
flaggedPlayers.remove(playerName);
diff --git a/src/main/java/com/aluer/security/AntiGriefDetectionService.java b/src/main/java/com/aluer/security/AntiGriefDetectionService.java
index 811b388..e93b439 100644
--- a/src/main/java/com/aluer/security/AntiGriefDetectionService.java
+++ b/src/main/java/com/aluer/security/AntiGriefDetectionService.java
@@ -1,5 +1,6 @@
package com.aluer.security;
+import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service;
import java.time.Instant;
@@ -13,6 +14,8 @@ import java.util.concurrent.atomic.AtomicLong;
@Service
public class AntiGriefDetectionService {
+ private final ServerGuardConfig config;
+
private final Map playerActivities = new ConcurrentHashMap<>();
private final Map> griefEvents = new ConcurrentHashMap<>();
private final Map flaggedPlayers = new ConcurrentHashMap<>();
@@ -39,10 +42,19 @@ public class AntiGriefDetectionService {
);
public AntiGriefDetectionService() {
+ this(new ServerGuardConfig());
+ }
+
+ public AntiGriefDetectionService(ServerGuardConfig config) {
+ this.config = config;
scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS);
}
public GriefCheckResult checkBlockBreak(String playerName, String blockType, int x, int y, int z, String world) {
+ if (!config.getSecurity().getSuperEvolution().isAntiGrief()) {
+ return GriefCheckResult.clean();
+ }
+
PlayerActivity activity = playerActivities.computeIfAbsent(playerName, k -> new PlayerActivity());
activity.recordBlockBreak(blockType, x, y, z, world);
diff --git a/src/main/java/com/aluer/security/AntiSkinSpoofService.java b/src/main/java/com/aluer/security/AntiSkinSpoofService.java
new file mode 100644
index 0000000..69715bb
--- /dev/null
+++ b/src/main/java/com/aluer/security/AntiSkinSpoofService.java
@@ -0,0 +1,255 @@
+package com.aluer.security;
+
+import com.aluer.config.ServerGuardConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+
+@Service
+public class AntiSkinSpoofService {
+
+ private final ServerGuardConfig config;
+
+ private final Map> skinChangeTimestamps = new ConcurrentHashMap<>();
+ private final Map lastKnownSkinHash = new ConcurrentHashMap<>();
+ private final Map spoofScoreByPlayer = new ConcurrentHashMap<>();
+
+ private final AtomicLong totalChecks = new AtomicLong(0);
+ private final AtomicLong spoofsDetected = new AtomicLong(0);
+ private final AtomicLong knownImpersonators = new AtomicLong(0);
+ private final AtomicLong suspiciousCount = new AtomicLong(0);
+
+ private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+
+ // Known impersonation targets (famous Minecraft personalities)
+ private static final Set KNOWN_IMPERSONATION_TARGETS = Set.of(
+ "Dream", "Technoblade", "TommyInnit", "GeorgeNotFound", "Sapnap",
+ "Punz", "BadBoyHalo", "Philza", "WilburSoot", "Ranboo",
+ "Tubbo", "CaptainPuffy", "Awesamdude", "Quackity", "KarlJacobs",
+ "NikiNihachu", "Fundy", "Eret", "Hbomb94", "Antfrost",
+ "Notch", "Jeb_", "Dinnerbone", "Mojang", "Herobrine"
+ );
+
+ // Known cheat client skin hashes (example patterns)
+ private static final Set KNOWN_CHEAT_SKIN_PREFIXES = Set.of(
+ "deadbeef", "cafebabe", "0badc0de", "1337c0d3"
+ );
+
+ // Suspicious URL patterns
+ private static final List SUSPICIOUS_URL_PATTERNS = List.of(
+ "data:", "localhost", "127.0.0.1", "0.0.0.0",
+ "discord", "mediafire", "mega.nz", "dropbox",
+ "pastebin", "gyazo", "imgur", "prnt.sc"
+ );
+
+ private static final String MOJANG_SKIN_URL_PREFIX = "http://textures.minecraft.net/texture/";
+ private static final String MOJANG_SKIN_URL_HTTPS = "https://textures.minecraft.net/texture/";
+
+ private static final int MAX_SKIN_CHANGES_IN_5MIN = 3;
+ private static final int MAX_SKIN_CHANGES_IN_1HOUR = 10;
+ private static final int SPOOF_SCORE_THRESHOLD = 60;
+
+ public AntiSkinSpoofService() {
+ this(new ServerGuardConfig());
+ }
+
+ @Autowired
+ public AntiSkinSpoofService(ServerGuardConfig config) {
+ this.config = config;
+ scheduler.scheduleAtFixedRate(this::cleanupStale, 300, 300, TimeUnit.SECONDS);
+ }
+
+ public SkinSpoofResult checkSkin(String playerName, String skinUrl, String modelType, String skinHash) {
+ if (!config.getSecurity().getSuperEvolution().isAntiSkinSpoof()) {
+ return SkinSpoofResult.clean();
+ }
+
+ totalChecks.incrementAndGet();
+ long now = System.currentTimeMillis();
+ List reasons = new ArrayList<>();
+ int score = 0;
+
+ // 1. Null/invalid skin detection
+ if (skinUrl == null || skinUrl.isEmpty() || skinHash == null || skinHash.isEmpty()) {
+ score += 20;
+ reasons.add("NULL_SKIN: Missing skin data");
+ }
+
+ // 2. Suspicious skin URL detection
+ if (skinUrl != null && isSuspiciousUrl(skinUrl)) {
+ score += 25;
+ reasons.add("SUSPICIOUS_URL: " + skinUrl);
+ }
+
+ // 3. Non-Mojang URL detection
+ if (skinUrl != null && !skinUrl.isEmpty()
+ && !skinUrl.startsWith(MOJANG_SKIN_URL_PREFIX)
+ && !skinUrl.startsWith(MOJANG_SKIN_URL_HTTPS)) {
+ score += 15;
+ reasons.add("NON_MOJANG_URL: " + skinUrl);
+ }
+
+ // 4. Known cheat client skin
+ if (skinHash != null && isKnownCheatSkinHash(skinHash)) {
+ score += 30;
+ reasons.add("CHEAT_CLIENT_SKIN_HASH: " + skinHash);
+ }
+
+ // 5. Skin change frequency monitoring
+ List changes = skinChangeTimestamps.computeIfAbsent(playerName, k -> new CopyOnWriteArrayList<>());
+ changes.add(now);
+
+ long cutoff5Min = now - (5 * 60 * 1000L);
+ long recent5Min = changes.stream().filter(t -> t > cutoff5Min).count();
+ if (recent5Min > MAX_SKIN_CHANGES_IN_5MIN) {
+ score += 20;
+ reasons.add("RAPID_CHANGE_5MIN: " + recent5Min + " changes in 5 minutes");
+ }
+
+ long cutoff1Hour = now - (60 * 60 * 1000L);
+ long recent1Hour = changes.stream().filter(t -> t > cutoff1Hour).count();
+ if (recent1Hour > MAX_SKIN_CHANGES_IN_1HOUR) {
+ score += 15;
+ reasons.add("RAPID_CHANGE_1HOUR: " + recent1Hour + " changes in 1 hour");
+ }
+
+ // 6. Known impersonation target
+ if (skinHash != null && isKnownImpersonationSkin(playerName, skinHash)) {
+ score += 35;
+ reasons.add("IMPERSONATION: Skin matches known personality " + playerName);
+ knownImpersonators.incrementAndGet();
+ }
+
+ // 7. Slim vs Classic model mismatch (some hacked clients use wrong model)
+ if (modelType != null && skinHash != null) {
+ String lastHash = lastKnownSkinHash.get(playerName);
+ if (lastHash != null && !lastHash.equals(skinHash)) {
+ // hash changed but model type might be incorrect
+ if ("slim".equalsIgnoreCase(modelType)) {
+ score += 10;
+ reasons.add("MODEL_SWITCH_SLIM: Possible spoofed slim model");
+ }
+ }
+ }
+
+ if (skinHash != null) {
+ lastKnownSkinHash.put(playerName, skinHash);
+ }
+
+ // Cumulative spoof score
+ int existingScore = spoofScoreByPlayer.getOrDefault(playerName, 0);
+ spoofScoreByPlayer.put(playerName, existingScore + score);
+
+ // Determine result
+ if (score >= SPOOF_SCORE_THRESHOLD || (existingScore + score) >= SPOOF_SCORE_THRESHOLD) {
+ spoofsDetected.incrementAndGet();
+ return SkinSpoofResult.spoofed(reasons);
+ }
+
+ if (score > 0) {
+ suspiciousCount.incrementAndGet();
+ return SkinSpoofResult.suspicious(reasons);
+ }
+
+ return SkinSpoofResult.clean();
+ }
+
+ public void resetPlayer(String playerName) {
+ skinChangeTimestamps.remove(playerName);
+ lastKnownSkinHash.remove(playerName);
+ spoofScoreByPlayer.remove(playerName);
+ }
+
+ public Map getStatus() {
+ Map status = new LinkedHashMap<>();
+ status.put("totalChecks", totalChecks.get());
+ status.put("spoofsDetected", spoofsDetected.get());
+ status.put("knownImpersonators", knownImpersonators.get());
+ status.put("suspiciousCount", suspiciousCount.get());
+ status.put("trackedPlayers", skinChangeTimestamps.size());
+ status.put("configEnabled", config.getSecurity().getSuperEvolution().isAntiSkinSpoof());
+ return status;
+ }
+
+ private boolean isSuspiciousUrl(String url) {
+ if (url.startsWith("data:")) return true;
+ String lower = url.toLowerCase();
+ for (String pattern : SUSPICIOUS_URL_PATTERNS) {
+ if (lower.contains(pattern)) return true;
+ }
+ return false;
+ }
+
+ private boolean isKnownCheatSkinHash(String hash) {
+ if (hash == null) return false;
+ String lower = hash.toLowerCase();
+ for (String prefix : KNOWN_CHEAT_SKIN_PREFIXES) {
+ if (lower.startsWith(prefix)) return true;
+ }
+ return false;
+ }
+
+ private boolean isKnownImpersonationSkin(String playerName, String skinHash) {
+ // Check if the player name (lowercased) matches a known target
+ // but the skin data is suspicious — flag it
+ if (playerName == null) return false;
+ for (String target : KNOWN_IMPERSONATION_TARGETS) {
+ if (playerName.equalsIgnoreCase(target)) {
+ // Player is using the exact name of a famous personality
+ return true;
+ }
+ // Also detect impersonation-like names (e.g., "Dream_", "Notch123")
+ String lowerName = playerName.toLowerCase();
+ String lowerTarget = target.toLowerCase();
+ if (lowerName.contains(lowerTarget) && !lowerName.equals(lowerTarget)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void cleanupStale() {
+ long cutoff = System.currentTimeMillis() - (2 * 60 * 60 * 1000L); // 2 hours
+ skinChangeTimestamps.entrySet().removeIf(e -> {
+ e.getValue().removeIf(t -> t < cutoff);
+ return e.getValue().isEmpty();
+ });
+ // Also clear spoof scores for inactive players
+ spoofScoreByPlayer.entrySet().removeIf(e -> !skinChangeTimestamps.containsKey(e.getKey()));
+ }
+
+ // -- SkinSpoofResult --
+ public static class SkinSpoofResult {
+ private final boolean clean;
+ private final boolean suspicious;
+ private final boolean spoofed;
+ private final List reasons;
+
+ private SkinSpoofResult(boolean clean, boolean suspicious, boolean spoofed, List reasons) {
+ this.clean = clean;
+ this.suspicious = suspicious;
+ this.spoofed = spoofed;
+ this.reasons = reasons;
+ }
+
+ public static SkinSpoofResult clean() {
+ return new SkinSpoofResult(true, false, false, List.of());
+ }
+
+ public static SkinSpoofResult suspicious(List reasons) {
+ return new SkinSpoofResult(false, true, false, reasons);
+ }
+
+ public static SkinSpoofResult spoofed(List reasons) {
+ return new SkinSpoofResult(false, false, true, reasons);
+ }
+
+ public boolean isClean() { return clean; }
+ public boolean isSuspicious() { return suspicious; }
+ public boolean isSpoofed() { return spoofed; }
+ public List getReasons() { return reasons; }
+ }
+}
diff --git a/src/main/java/com/aluer/security/AntiXrayDetectionService.java b/src/main/java/com/aluer/security/AntiXrayDetectionService.java
index f630245..9607ae1 100644
--- a/src/main/java/com/aluer/security/AntiXrayDetectionService.java
+++ b/src/main/java/com/aluer/security/AntiXrayDetectionService.java
@@ -1,5 +1,6 @@
package com.aluer.security;
+import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service;
import java.time.Instant;
@@ -13,6 +14,8 @@ import java.util.concurrent.atomic.AtomicLong;
@Service
public class AntiXrayDetectionService {
+ private final ServerGuardConfig config;
+
private final Map miningData = new ConcurrentHashMap<>();
private final Map flaggedPlayers = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
@@ -47,10 +50,19 @@ public class AntiXrayDetectionService {
);
public AntiXrayDetectionService() {
+ this(new ServerGuardConfig());
+ }
+
+ public AntiXrayDetectionService(ServerGuardConfig config) {
+ this.config = config;
scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS);
}
public XrayCheckResult checkBlockBreak(String playerName, String blockType, int x, int y, int z, String world) {
+ if (!config.getSecurity().getSuperEvolution().isAntiXray()) {
+ return XrayCheckResult.clean();
+ }
+
if (flaggedPlayers.containsKey(playerName)) {
if (Instant.now().isAfter(flaggedPlayers.get(playerName))) {
flaggedPlayers.remove(playerName);
diff --git a/src/main/java/com/aluer/security/BackupIntegrityService.java b/src/main/java/com/aluer/security/BackupIntegrityService.java
new file mode 100644
index 0000000..9dfc616
--- /dev/null
+++ b/src/main/java/com/aluer/security/BackupIntegrityService.java
@@ -0,0 +1,232 @@
+package com.aluer.security;
+
+import com.aluer.config.ServerGuardConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.*;
+import java.nio.file.*;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+
+@Service
+public class BackupIntegrityService {
+
+ private final ServerGuardConfig config;
+
+ private final Map records = new ConcurrentHashMap<>();
+ private final Map> history = new ConcurrentHashMap<>();
+
+ private final AtomicLong totalVerified = new AtomicLong(0);
+ private final AtomicLong failedCount = new AtomicLong(0);
+ private final AtomicLong corruptedCount = new AtomicLong(0);
+
+ private volatile long lastScanTime = 0L;
+
+ private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+
+ private static final long SIX_HOURS_MS = 6L * 60 * 60 * 1000;
+ private static final long SIZE_TOLERANCE_BYTES = 1024L;
+
+ public BackupIntegrityService() {
+ this(new ServerGuardConfig());
+ }
+
+ @Autowired
+ public BackupIntegrityService(ServerGuardConfig config) {
+ this.config = config;
+ scheduler.scheduleAtFixedRate(this::scanAll, 1, 6, TimeUnit.HOURS);
+ }
+
+ public void registerBackup(String name, String filePath, long size, String expectedHash) {
+ BackupRecord record = new BackupRecord(name, filePath, size, expectedHash, System.currentTimeMillis());
+ records.put(name, record);
+ history.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
+ }
+
+ public IntegrityResult verifyBackup(String name) {
+ if (!config.getSecurity().getSuperEvolution().isBackupIntegrity()) {
+ return IntegrityResult.passed();
+ }
+
+ BackupRecord record = records.get(name);
+ if (record == null) {
+ failedCount.incrementAndGet();
+ return IntegrityResult.missing();
+ }
+
+ Path path = Paths.get(record.filePath);
+ if (!Files.exists(path)) {
+ failedCount.incrementAndGet();
+ List reasons = new ArrayList<>();
+ reasons.add("File not found: " + record.filePath);
+ IntegrityResult result = IntegrityResult.failed(reasons);
+ appendHistory(name, result);
+ return result;
+ }
+
+ List reasons = new ArrayList<>();
+ boolean ok = true;
+
+ // File size check
+ try {
+ long actualSize = Files.size(path);
+ long diff = Math.abs(actualSize - record.expectedSize);
+ if (diff > SIZE_TOLERANCE_BYTES) {
+ ok = false;
+ reasons.add("Size mismatch: expected " + record.expectedSize + ", actual " + actualSize + " (diff " + diff + ")");
+ }
+ } catch (IOException e) {
+ ok = false;
+ reasons.add("Cannot read file size: " + e.getMessage());
+ }
+
+ // SHA-256 checksum
+ try {
+ String actualHash = computeSha256(path);
+ if (!actualHash.equals(record.expectedHash)) {
+ ok = false;
+ reasons.add("Hash mismatch: expected " + record.expectedHash + ", actual " + actualHash);
+ corruptedCount.incrementAndGet();
+ }
+ } catch (Exception e) {
+ ok = false;
+ reasons.add("Cannot compute hash: " + e.getMessage());
+ corruptedCount.incrementAndGet();
+ }
+
+ record.lastVerified = System.currentTimeMillis();
+
+ if (ok) {
+ totalVerified.incrementAndGet();
+ IntegrityResult result = IntegrityResult.passed();
+ appendHistory(name, result);
+ return result;
+ } else {
+ failedCount.incrementAndGet();
+ IntegrityResult result;
+ if (reasons.stream().anyMatch(r -> r.contains("Hash mismatch") || r.contains("corrupt"))) {
+ result = IntegrityResult.corrupted(reasons);
+ } else {
+ result = IntegrityResult.failed(reasons);
+ }
+ appendHistory(name, result);
+ return result;
+ }
+ }
+
+ public void scanAll() {
+ lastScanTime = System.currentTimeMillis();
+ for (String name : new ArrayList<>(records.keySet())) {
+ verifyBackup(name);
+ }
+ }
+
+ public Map getStatus() {
+ Map status = new LinkedHashMap<>();
+ status.put("totalBackups", records.size());
+ status.put("verifiedCount", totalVerified.get());
+ status.put("failedCount", failedCount.get());
+ status.put("corruptedCount", corruptedCount.get());
+ status.put("lastScanTime", lastScanTime > 0 ? new java.util.Date(lastScanTime).toString() : "never");
+ status.put("configEnabled", config.getSecurity().getSuperEvolution().isBackupIntegrity());
+ return status;
+ }
+
+ public Map getRecords() {
+ return new LinkedHashMap<>(records);
+ }
+
+ public List getHistory(String name) {
+ List h = history.get(name);
+ return h != null ? new ArrayList<>(h) : List.of();
+ }
+
+ private String computeSha256(Path path) throws IOException, NoSuchAlgorithmException {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ try (InputStream is = Files.newInputStream(path)) {
+ byte[] buffer = new byte[8192];
+ int read;
+ while ((read = is.read(buffer)) != -1) {
+ digest.update(buffer, 0, read);
+ }
+ }
+ byte[] hashBytes = digest.digest();
+ StringBuilder sb = new StringBuilder();
+ for (byte b : hashBytes) {
+ sb.append(String.format("%02x", b));
+ }
+ return sb.toString();
+ }
+
+ private void appendHistory(String name, IntegrityResult result) {
+ List list = history.get(name);
+ if (list != null) {
+ list.add(result);
+ while (list.size() > 50) {
+ list.remove(0);
+ }
+ }
+ }
+
+ // -- BackupRecord --
+ public static class BackupRecord {
+ public final String name;
+ public final String filePath;
+ public final long expectedSize;
+ public final String expectedHash;
+ public final long registeredAt;
+ public volatile long lastVerified;
+
+ BackupRecord(String name, String filePath, long expectedSize, String expectedHash, long registeredAt) {
+ this.name = name;
+ this.filePath = filePath;
+ this.expectedSize = expectedSize;
+ this.expectedHash = expectedHash;
+ this.registeredAt = registeredAt;
+ this.lastVerified = 0L;
+ }
+ }
+
+ // -- IntegrityResult --
+ public static class IntegrityResult {
+ private final boolean passed;
+ private final boolean failed;
+ private final boolean corrupted;
+ private final boolean missing;
+ private final List reasons;
+
+ private IntegrityResult(boolean passed, boolean failed, boolean corrupted, boolean missing, List reasons) {
+ this.passed = passed;
+ this.failed = failed;
+ this.corrupted = corrupted;
+ this.missing = missing;
+ this.reasons = reasons;
+ }
+
+ public static IntegrityResult passed() {
+ return new IntegrityResult(true, false, false, false, List.of());
+ }
+
+ public static IntegrityResult failed(List reasons) {
+ return new IntegrityResult(false, true, false, false, reasons);
+ }
+
+ public static IntegrityResult corrupted(List reasons) {
+ return new IntegrityResult(false, false, true, false, reasons);
+ }
+
+ public static IntegrityResult missing() {
+ return new IntegrityResult(false, false, false, true, List.of("Backup record not found"));
+ }
+
+ public boolean isPassed() { return passed; }
+ public boolean isFailed() { return failed; }
+ public boolean isCorrupted() { return corrupted; }
+ public boolean isMissing() { return missing; }
+ public List getReasons() { return reasons; }
+ }
+}
diff --git a/src/main/java/com/aluer/security/BruteForceProtectionService.java b/src/main/java/com/aluer/security/BruteForceProtectionService.java
index 6efeb33..43fe0b4 100644
--- a/src/main/java/com/aluer/security/BruteForceProtectionService.java
+++ b/src/main/java/com/aluer/security/BruteForceProtectionService.java
@@ -1,5 +1,7 @@
package com.aluer.security;
+import com.aluer.config.ServerGuardConfig;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.Instant;
@@ -13,6 +15,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Service
public class BruteForceProtectionService {
+ private final ServerGuardConfig config;
private final Map failureTracker = new ConcurrentHashMap<>();
private final Map blockedAccounts = new ConcurrentHashMap<>();
private final Map> ipAttemptTimeline = new ConcurrentHashMap<>();
@@ -40,10 +43,20 @@ public class BruteForceProtectionService {
private final AtomicLong totalBruteForceDetections = new AtomicLong(0);
public BruteForceProtectionService() {
+ this(new ServerGuardConfig());
+ }
+
+ @Autowired
+ public BruteForceProtectionService(ServerGuardConfig config) {
+ this.config = config;
scheduler.scheduleAtFixedRate(this::cleanupExpired, 60, 120, TimeUnit.SECONDS);
}
public AuthResult checkLoginAttempt(String username, String ip, String source) {
+ if (!config.getSecurity().getSuperEvolution().isBruteForce()) {
+ return AuthResult.allowed(0);
+ }
+
if (isBlocked(username)) {
return AuthResult.blocked("Account " + username + " is temporarily locked due to excessive failed attempts");
}
diff --git a/src/main/java/com/aluer/security/CSPEnforcementService.java b/src/main/java/com/aluer/security/CSPEnforcementService.java
index 6e0679d..d8fc6c4 100644
--- a/src/main/java/com/aluer/security/CSPEnforcementService.java
+++ b/src/main/java/com/aluer/security/CSPEnforcementService.java
@@ -1,5 +1,6 @@
package com.aluer.security;
+import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service;
import java.time.Instant;
@@ -10,9 +11,18 @@ import java.util.concurrent.atomic.AtomicLong;
@Service
public class CSPEnforcementService {
+ private final ServerGuardConfig config;
private final Map> violations = new ConcurrentHashMap<>();
private final AtomicLong totalViolations = new AtomicLong(0);
+ public CSPEnforcementService() {
+ this(new ServerGuardConfig());
+ }
+
+ public CSPEnforcementService(ServerGuardConfig config) {
+ this.config = config;
+ }
+
private static final String DEFAULT_CSP_HEADER = String.join("; ",
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval'",
@@ -57,6 +67,7 @@ public class CSPEnforcementService {
);
public CSPCheckResult checkRequest(String uri, Map headers, String body) {
+ if (!config.getSecurity().getSuperEvolution().isCsp()) return CSPCheckResult.clean();
List matched = new ArrayList<>();
int score = 0;
diff --git a/src/main/java/com/aluer/security/ComplianceScannerService.java b/src/main/java/com/aluer/security/ComplianceScannerService.java
index 4292868..534cba3 100644
--- a/src/main/java/com/aluer/security/ComplianceScannerService.java
+++ b/src/main/java/com/aluer/security/ComplianceScannerService.java
@@ -1,5 +1,6 @@
package com.aluer.security;
+import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service;
import java.time.Instant;
@@ -13,15 +14,26 @@ import java.util.concurrent.atomic.AtomicLong;
@Service
public class ComplianceScannerService {
+ private final ServerGuardConfig config;
+
private final Map> findings = new ConcurrentHashMap<>();
private final AtomicLong totalFindings = new AtomicLong(0);
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
public ComplianceScannerService() {
+ this(new ServerGuardConfig());
+ }
+
+ public ComplianceScannerService(ServerGuardConfig config) {
+ this.config = config;
scheduler.scheduleAtFixedRate(this::runComplianceScan, 120, 86400, TimeUnit.SECONDS);
}
public ComplianceReport runComplianceScan() {
+ if (!config.getSecurity().getSuperEvolution().isCompliance()) {
+ return new ComplianceReport(Instant.now(), List.of(), 0.0);
+ }
+
List allFindings = new ArrayList<>();
Instant scanTime = Instant.now();
diff --git a/src/main/java/com/aluer/security/ConnectionThrottleService.java b/src/main/java/com/aluer/security/ConnectionThrottleService.java
new file mode 100644
index 0000000..1bf2d2b
--- /dev/null
+++ b/src/main/java/com/aluer/security/ConnectionThrottleService.java
@@ -0,0 +1,213 @@
+package com.aluer.security;
+
+import com.aluer.config.ServerGuardConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+
+@Service
+public class ConnectionThrottleService {
+
+ private final ServerGuardConfig config;
+
+ private final Map> connectionTimestamps = new ConcurrentHashMap<>();
+ private final Map excessCounters = new ConcurrentHashMap<>();
+ private final Map lastActivity = new ConcurrentHashMap<>();
+ private final Map delayedUntil = new ConcurrentHashMap<>();
+
+ private final AtomicLong totalConnections = new AtomicLong(0);
+ private final AtomicLong blockedCount = new AtomicLong(0);
+ private final AtomicLong delayedCount = new AtomicLong(0);
+
+ private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+
+ private static final long WINDOW_5S = 5000L;
+ private static final long WINDOW_60S = 60000L;
+ private static final long WINDOW_300S = 300000L;
+ private static final long BURST_WINDOW_MS = 1000L;
+ private static final int BURST_THRESHOLD = 3;
+ private static final int MAX_CONNECTIONS_PER_IP = 5;
+ private static final long STALE_THRESHOLD_MS = 600000L;
+
+ private static final long DELAY_1ST_MS = 2000L;
+ private static final long DELAY_2ND_MS = 5000L;
+ private static final long DELAY_3RD_MS = 15000L;
+ private static final long DELAY_4TH_MS = 30000L;
+
+ public ConnectionThrottleService() {
+ this(new ServerGuardConfig());
+ }
+
+ @Autowired
+ public ConnectionThrottleService(ServerGuardConfig config) {
+ this.config = config;
+ scheduler.scheduleAtFixedRate(this::cleanupStale, 60, 60, TimeUnit.SECONDS);
+ }
+
+ public ThrottleResult tryAcquire(String ip) {
+ if (!config.getSecurity().getSuperEvolution().isConnectionThrottle()) {
+ return ThrottleResult.allowed();
+ }
+
+ totalConnections.incrementAndGet();
+ long now = System.currentTimeMillis();
+
+ ThrottleResult delayed = checkExistingDelay(ip, now);
+ if (delayed != null) {
+ return delayed;
+ }
+
+ List timestamps = connectionTimestamps.computeIfAbsent(ip, k -> new CopyOnWriteArrayList<>());
+ timestamps.add(now);
+ lastActivity.put(ip, now);
+
+ ThrottleResult rejection = checkWindows(ip, timestamps, now);
+ if (rejection != null) {
+ blockedCount.incrementAndGet();
+ return rejection;
+ }
+
+ return ThrottleResult.allowed();
+ }
+
+ private ThrottleResult checkExistingDelay(String ip, long now) {
+ Long delayUntil = delayedUntil.get(ip);
+ if (delayUntil != null && delayUntil > now) {
+ long remainingMs = delayUntil - now;
+ return ThrottleResult.delayed(remainingMs);
+ }
+ if (delayUntil != null && delayUntil <= now) {
+ delayedUntil.remove(ip);
+ }
+ return null;
+ }
+
+ private ThrottleResult checkWindows(String ip, List timestamps, long now) {
+ // Burst detection: 3+ connections in 1 second
+ long burstCutoff = now - BURST_WINDOW_MS;
+ long burstCount = timestamps.stream().filter(t -> t > burstCutoff).count();
+ if (burstCount >= BURST_THRESHOLD) {
+ String reason = "Connection burst: " + burstCount + " connections in " + BURST_WINDOW_MS + "ms";
+ return ThrottleResult.rejected(reason);
+ }
+
+ // Max connections per IP
+ long maxCutoff = now - WINDOW_300S;
+ long totalForIp = timestamps.stream().filter(t -> t > maxCutoff).count();
+ if (totalForIp > MAX_CONNECTIONS_PER_IP) {
+ String reason = "Max connections per IP exceeded: " + totalForIp + " > " + MAX_CONNECTIONS_PER_IP;
+ return ThrottleResult.rejected(reason);
+ }
+
+ // Window-based throttling
+ long window5sCutoff = now - WINDOW_5S;
+ long count5s = timestamps.stream().filter(t -> t > window5sCutoff).count();
+ long window60sCutoff = now - WINDOW_60S;
+ long count60s = timestamps.stream().filter(t -> t > window60sCutoff).count();
+ long window300sCutoff = now - WINDOW_300S;
+ long count300s = timestamps.stream().filter(t -> t > window300sCutoff).count();
+
+ int excessLevel = excessCounters.getOrDefault(ip, 0);
+
+ boolean thresholdExceeded = false;
+ if (count5s > 2) thresholdExceeded = true;
+ if (count60s > 8) thresholdExceeded = true;
+ if (count300s > 20) thresholdExceeded = true;
+
+ if (thresholdExceeded) {
+ excessCounters.put(ip, excessLevel + 1);
+ long delayMs;
+ switch (excessLevel) {
+ case 0: delayMs = DELAY_1ST_MS; break;
+ case 1: delayMs = DELAY_2ND_MS; break;
+ case 2: delayMs = DELAY_3RD_MS; break;
+ default: delayMs = DELAY_4TH_MS; break;
+ }
+ delayedUntil.put(ip, now + delayMs);
+ delayedCount.incrementAndGet();
+ String reason = "Progressive delay: excess level " + (excessLevel + 1) + ", delay " + delayMs + "ms";
+ return ThrottleResult.rejected(reason);
+ }
+
+ return null;
+ }
+
+ private void cleanupStale() {
+ long now = System.currentTimeMillis();
+ List staleIPs = new ArrayList<>();
+ for (Map.Entry entry : lastActivity.entrySet()) {
+ if (now - entry.getValue() > STALE_THRESHOLD_MS) {
+ staleIPs.add(entry.getKey());
+ }
+ }
+ for (String ip : staleIPs) {
+ connectionTimestamps.remove(ip);
+ excessCounters.remove(ip);
+ lastActivity.remove(ip);
+ delayedUntil.remove(ip);
+ }
+ }
+
+ public Map getStatus() {
+ Map status = new LinkedHashMap<>();
+ status.put("totalConnections", totalConnections.get());
+ status.put("blockedCount", blockedCount.get());
+ status.put("delayedCount", delayedCount.get());
+ status.put("activeIPs", connectionTimestamps.size());
+ status.put("delayedIPs", delayedUntil.size());
+ status.put("configEnabled", config.getSecurity().getSuperEvolution().isConnectionThrottle());
+ return status;
+ }
+
+ // -- DelayedResult used internally (exposed for caller clarity) --
+ private static class DelayedResult extends ThrottleResult {
+ DelayedResult(long delayMs) {
+ super(false, true, false, delayMs, null);
+ }
+ }
+
+ // -- RejectionResult used internally --
+ private static class RejectionResult extends ThrottleResult {
+ RejectionResult(String reason) {
+ super(false, false, true, 0, reason);
+ }
+ }
+
+ // -- ThrottleResult --
+ public static class ThrottleResult {
+ private final boolean allowed;
+ private final boolean delayed;
+ private final boolean rejected;
+ private final long delayMs;
+ private final String reason;
+
+ private ThrottleResult(boolean allowed, boolean delayed, boolean rejected, long delayMs, String reason) {
+ this.allowed = allowed;
+ this.delayed = delayed;
+ this.rejected = rejected;
+ this.delayMs = delayMs;
+ this.reason = reason;
+ }
+
+ public static ThrottleResult allowed() {
+ return new ThrottleResult(true, false, false, 0, null);
+ }
+
+ public static ThrottleResult delayed(long delayMs) {
+ return new ThrottleResult(false, true, false, delayMs, "Delayed for " + delayMs + "ms");
+ }
+
+ public static ThrottleResult rejected(String reason) {
+ return new ThrottleResult(false, false, true, 0, reason);
+ }
+
+ public boolean isAllowed() { return allowed; }
+ public boolean isDelayed() { return delayed; }
+ public boolean isRejected() { return rejected; }
+ public long getDelayMs() { return delayMs; }
+ public String getReason() { return reason; }
+ }
+}
diff --git a/src/main/java/com/aluer/security/CrashExploitProtectionService.java b/src/main/java/com/aluer/security/CrashExploitProtectionService.java
index 7a136ae..720c96b 100644
--- a/src/main/java/com/aluer/security/CrashExploitProtectionService.java
+++ b/src/main/java/com/aluer/security/CrashExploitProtectionService.java
@@ -1,5 +1,6 @@
package com.aluer.security;
+import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service;
import java.time.Instant;
@@ -10,6 +11,8 @@ import java.util.concurrent.atomic.AtomicLong;
@Service
public class CrashExploitProtectionService {
+ private final ServerGuardConfig config;
+
private final Map> crashAttempts = new ConcurrentHashMap<>();
private final Map blockedSources = new ConcurrentHashMap<>();
private final AtomicLong totalDetections = new AtomicLong(0);
@@ -46,7 +49,19 @@ public class CrashExploitProtectionService {
"Particle crash", "Particle flood causing GPU/CPU exhaustion", 75, false));
}
+ public CrashExploitProtectionService() {
+ this(new ServerGuardConfig());
+ }
+
+ public CrashExploitProtectionService(ServerGuardConfig config) {
+ this.config = config;
+ }
+
public CrashCheckResult checkPacket(String packetType, int packetSize, String sourceIP) {
+ if (!config.getSecurity().getSuperEvolution().isCrashExploit()) {
+ return CrashCheckResult.clean();
+ }
+
if (blockedSources.containsKey(sourceIP)) {
if (Instant.now().isAfter(blockedSources.get(sourceIP))) {
blockedSources.remove(sourceIP);
diff --git a/src/main/java/com/aluer/security/DNSTunnelDetectionService.java b/src/main/java/com/aluer/security/DNSTunnelDetectionService.java
index 722d27e..8077673 100644
--- a/src/main/java/com/aluer/security/DNSTunnelDetectionService.java
+++ b/src/main/java/com/aluer/security/DNSTunnelDetectionService.java
@@ -1,5 +1,7 @@
package com.aluer.security;
+import com.aluer.config.ServerGuardConfig;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.Instant;
@@ -13,6 +15,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Service
public class DNSTunnelDetectionService {
+ private final ServerGuardConfig config;
private final Map queryTrackers = new ConcurrentHashMap<>();
private final Map blockedSources = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
@@ -31,10 +34,20 @@ public class DNSTunnelDetectionService {
".pw", ".club", ".work", ".date", ".bid", ".surf");
public DNSTunnelDetectionService() {
+ this(new ServerGuardConfig());
+ }
+
+ @Autowired
+ public DNSTunnelDetectionService(ServerGuardConfig config) {
+ this.config = config;
scheduler.scheduleAtFixedRate(this::cleanupExpired, 120, 300, TimeUnit.SECONDS);
}
public TunnelCheckResult checkDNSQuery(String sourceIP, String domain, String queryType) {
+ if (!config.getSecurity().getSuperEvolution().isDnsTunnel()) {
+ return TunnelCheckResult.clean();
+ }
+
if (blockedSources.containsKey(sourceIP)) {
if (Instant.now().isAfter(blockedSources.get(sourceIP))) {
blockedSources.remove(sourceIP);
diff --git a/src/main/java/com/aluer/security/DataLossPreventionService.java b/src/main/java/com/aluer/security/DataLossPreventionService.java
index ed12c9f..ef0c964 100644
--- a/src/main/java/com/aluer/security/DataLossPreventionService.java
+++ b/src/main/java/com/aluer/security/DataLossPreventionService.java
@@ -1,5 +1,6 @@
package com.aluer.security;
+import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service;
import java.time.Instant;
@@ -11,9 +12,18 @@ import java.util.regex.Pattern;
@Service
public class DataLossPreventionService {
+ private final ServerGuardConfig config;
private final Map> detections = new ConcurrentHashMap<>();
private final AtomicLong totalDetections = new AtomicLong(0);
+ public DataLossPreventionService() {
+ this(new ServerGuardConfig());
+ }
+
+ public DataLossPreventionService(ServerGuardConfig config) {
+ this.config = config;
+ }
+
private static final List RULES = new ArrayList<>();
static {
@@ -67,6 +77,7 @@ public class DataLossPreventionService {
}
public DLPCheckResult scan(String content, String source, String channel) {
+ if (!config.getSecurity().getSuperEvolution().isDlp()) return DLPCheckResult.clean();
if (content == null || content.trim().isEmpty()) return DLPCheckResult.clean();
Map> findings = new LinkedHashMap<>();
diff --git a/src/main/java/com/aluer/security/DatabaseFirewallService.java b/src/main/java/com/aluer/security/DatabaseFirewallService.java
index d66edba..0d12cc1 100644
--- a/src/main/java/com/aluer/security/DatabaseFirewallService.java
+++ b/src/main/java/com/aluer/security/DatabaseFirewallService.java
@@ -1,5 +1,6 @@
package com.aluer.security;
+import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service;
import java.time.Instant;
@@ -10,10 +11,19 @@ import java.util.concurrent.atomic.AtomicLong;
@Service
public class DatabaseFirewallService {
+ private final ServerGuardConfig config;
private final Map> queryLog = new ConcurrentHashMap<>();
private final AtomicLong totalBlocked = new AtomicLong(0);
private final AtomicLong totalQueries = new AtomicLong(0);
+ public DatabaseFirewallService() {
+ this(new ServerGuardConfig());
+ }
+
+ public DatabaseFirewallService(ServerGuardConfig config) {
+ this.config = config;
+ }
+
private static final List DANGEROUS_KEYWORDS = List.of(
"DROP TABLE", "DROP DATABASE", "TRUNCATE TABLE", "DELETE FROM",
"ALTER TABLE", "ALTER DATABASE", "CREATE USER", "GRANT ALL",
@@ -28,6 +38,7 @@ public class DatabaseFirewallService {
private static final int MAX_JOIN_COUNT = 10;
public QueryCheckResult checkQuery(String sql, String source, String database) {
+ if (!config.getSecurity().getSuperEvolution().isDatabaseFirewall()) return QueryCheckResult.clean();
if (sql == null || sql.trim().isEmpty()) return QueryCheckResult.clean();
totalQueries.incrementAndGet();
diff --git a/src/main/java/com/aluer/security/ExploitSignatureService.java b/src/main/java/com/aluer/security/ExploitSignatureService.java
index adbdc3c..9573ded 100644
--- a/src/main/java/com/aluer/security/ExploitSignatureService.java
+++ b/src/main/java/com/aluer/security/ExploitSignatureService.java
@@ -1,5 +1,7 @@
package com.aluer.security;
+import com.aluer.config.ServerGuardConfig;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.Instant;
@@ -11,6 +13,7 @@ import java.util.regex.Pattern;
@Service
public class ExploitSignatureService {
+ private final ServerGuardConfig config;
private final Map> detections = new ConcurrentHashMap<>();
private final Map blockedSources = new ConcurrentHashMap<>();
private final AtomicLong totalDetections = new AtomicLong(0);
@@ -75,7 +78,20 @@ public class ExploitSignatureService {
"Suspicious server.properties tampering"));
}
+ public ExploitSignatureService() {
+ this(new ServerGuardConfig());
+ }
+
+ @Autowired
+ public ExploitSignatureService(ServerGuardConfig config) {
+ this.config = config;
+ }
+
public ExploitCheckResult scan(String content, String sourceIP, String context) {
+ if (!config.getSecurity().getSuperEvolution().isExploitSignature()) {
+ return ExploitCheckResult.clean();
+ }
+
if (content == null || content.trim().isEmpty()) return ExploitCheckResult.clean();
if (blockedSources.containsKey(sourceIP)) {
diff --git a/src/main/java/com/aluer/security/ForensicsCollectorService.java b/src/main/java/com/aluer/security/ForensicsCollectorService.java
index 0a574b4..a42fa94 100644
--- a/src/main/java/com/aluer/security/ForensicsCollectorService.java
+++ b/src/main/java/com/aluer/security/ForensicsCollectorService.java
@@ -1,5 +1,6 @@
package com.aluer.security;
+import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service;
import java.io.*;
@@ -12,18 +13,31 @@ import java.util.concurrent.atomic.AtomicLong;
@Service
public class ForensicsCollectorService {
+ private final ServerGuardConfig config;
private final Map cases = new ConcurrentHashMap<>();
private final Map> evidenceStore = new ConcurrentHashMap<>();
private final AtomicLong totalEvidenceCollected = new AtomicLong(0);
private final String evidenceDir = "./forensics";
public ForensicsCollectorService() {
+ this(new ServerGuardConfig());
+ }
+
+ public ForensicsCollectorService(ServerGuardConfig config) {
+ this.config = config;
try {
Files.createDirectories(Path.of(evidenceDir));
} catch (IOException ignored) {}
}
public ForensicsCase openCase(String caseName, String reason, String operator) {
+ if (!config.getSecurity().getSuperEvolution().isForensics()) {
+ ForensicsCase disabledCase = new ForensicsCase("forensics-module-disabled", caseName, reason, operator, Instant.now());
+ disabledCase.conclusion = "forensics-module-disabled";
+ disabledCase.closed = true;
+ disabledCase.closeTime = Instant.now();
+ return disabledCase;
+ }
String caseId = "CASE-" + Instant.now().getEpochSecond() + "-" + randomHex(6);
ForensicsCase fCase = new ForensicsCase(caseId, caseName, reason, operator, Instant.now());
cases.put(caseId, fCase);
diff --git a/src/main/java/com/aluer/security/GeoBlockService.java b/src/main/java/com/aluer/security/GeoBlockService.java
new file mode 100644
index 0000000..8c9259c
--- /dev/null
+++ b/src/main/java/com/aluer/security/GeoBlockService.java
@@ -0,0 +1,209 @@
+package com.aluer.security;
+
+import com.aluer.config.ServerGuardConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+
+@Service
+public class GeoBlockService {
+
+ private final ServerGuardConfig config;
+
+ private final Set allowlist = ConcurrentHashMap.newKeySet();
+ private final Set blocklist = ConcurrentHashMap.newKeySet();
+ private final Map ipCountryCache = new ConcurrentHashMap<>();
+ private final Map ipCountryCacheTimestamps = new ConcurrentHashMap<>();
+ private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+
+ private final AtomicLong totalChecked = new AtomicLong(0);
+ private final AtomicLong totalBlocked = new AtomicLong(0);
+ private final AtomicLong totalAllowed = new AtomicLong(0);
+
+ private static final long CACHE_TTL_MS = 3600000;
+ private volatile boolean defaultAllow = true;
+
+ private static final Set HIGH_RISK_COUNTRIES = Set.of("CN", "RU", "KP", "IR", "VN");
+
+ public GeoBlockService() {
+ this(new ServerGuardConfig());
+ }
+
+ @Autowired
+ public GeoBlockService(ServerGuardConfig config) {
+ this.config = config;
+ scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS);
+ }
+
+ public GeoBlockResult checkConnection(String ip, String countryCode) {
+ totalChecked.incrementAndGet();
+
+ if (!config.getSecurity().getSuperEvolution().isGeoBlock()) {
+ return GeoBlockResult.allowed();
+ }
+
+ if (ip == null || countryCode == null) {
+ totalBlocked.incrementAndGet();
+ return GeoBlockResult.blocked("Null IP or country code");
+ }
+
+ String normalizedCountry = countryCode.toUpperCase().trim();
+
+ // Cache the IP-to-country mapping
+ ipCountryCache.put(ip, normalizedCountry);
+ ipCountryCacheTimestamps.put(ip, System.currentTimeMillis());
+
+ // Check explicit blocklist first
+ if (blocklist.contains(normalizedCountry)) {
+ totalBlocked.incrementAndGet();
+ return GeoBlockResult.blocked("Country " + normalizedCountry + " is explicitly blocked");
+ }
+
+ // Check explicit allowlist
+ if (allowlist.contains(normalizedCountry)) {
+ totalAllowed.incrementAndGet();
+ return GeoBlockResult.allowed();
+ }
+
+ // Check high-risk countries
+ if (HIGH_RISK_COUNTRIES.contains(normalizedCountry)) {
+ totalBlocked.incrementAndGet();
+ return GeoBlockResult.blocked("Country " + normalizedCountry + " is a high-risk region for Minecraft attacks");
+ }
+
+ // Default mode
+ if (defaultAllow) {
+ totalAllowed.incrementAndGet();
+ return GeoBlockResult.allowed();
+ } else {
+ totalBlocked.incrementAndGet();
+ return GeoBlockResult.blocked("Country " + normalizedCountry + " not in allowlist (default-deny mode)");
+ }
+ }
+
+ public void addToAllowlist(String countryCode) {
+ if (countryCode != null) {
+ allowlist.add(countryCode.toUpperCase().trim());
+ blocklist.remove(countryCode.toUpperCase().trim());
+ }
+ }
+
+ public void removeFromAllowlist(String countryCode) {
+ if (countryCode != null) {
+ allowlist.remove(countryCode.toUpperCase().trim());
+ }
+ }
+
+ public void addToBlocklist(String countryCode) {
+ if (countryCode != null) {
+ blocklist.add(countryCode.toUpperCase().trim());
+ allowlist.remove(countryCode.toUpperCase().trim());
+ }
+ }
+
+ public void removeFromBlocklist(String countryCode) {
+ if (countryCode != null) {
+ blocklist.remove(countryCode.toUpperCase().trim());
+ }
+ }
+
+ public Set getAllowlist() {
+ return new HashSet<>(allowlist);
+ }
+
+ public Set getBlocklist() {
+ return new HashSet<>(blocklist);
+ }
+
+ public Set getHighRiskCountries() {
+ return new HashSet<>(HIGH_RISK_COUNTRIES);
+ }
+
+ public void setDefaultAllow(boolean defaultAllow) {
+ this.defaultAllow = defaultAllow;
+ }
+
+ public boolean isDefaultAllow() {
+ return defaultAllow;
+ }
+
+ public String getCachedCountry(String ip) {
+ Long timestamp = ipCountryCacheTimestamps.get(ip);
+ if (timestamp == null) {
+ return null;
+ }
+ if (System.currentTimeMillis() - timestamp > CACHE_TTL_MS) {
+ ipCountryCache.remove(ip);
+ ipCountryCacheTimestamps.remove(ip);
+ return null;
+ }
+ return ipCountryCache.get(ip);
+ }
+
+ public void cacheIpCountry(String ip, String countryCode) {
+ ipCountryCache.put(ip, countryCode.toUpperCase().trim());
+ ipCountryCacheTimestamps.put(ip, System.currentTimeMillis());
+ }
+
+ public Map getStatus() {
+ Map status = new LinkedHashMap<>();
+ status.put("enabled", config.getSecurity().getSuperEvolution().isGeoBlock());
+ status.put("defaultAllow", defaultAllow);
+ status.put("allowlistSize", allowlist.size());
+ status.put("blocklistSize", blocklist.size());
+ status.put("totalChecked", totalChecked.get());
+ status.put("totalBlocked", totalBlocked.get());
+ status.put("totalAllowed", totalAllowed.get());
+ status.put("cachedIPs", ipCountryCache.size());
+ status.put("allowlist", new ArrayList<>(allowlist));
+ status.put("blocklist", new ArrayList<>(blocklist));
+ status.put("highRiskCountries", new ArrayList<>(HIGH_RISK_COUNTRIES));
+ return status;
+ }
+
+ public long getTotalBlocked() {
+ return totalBlocked.get();
+ }
+
+ public long getTotalAllowed() {
+ return totalAllowed.get();
+ }
+
+ private void cleanupOldData() {
+ long cutoff = System.currentTimeMillis() - CACHE_TTL_MS;
+ ipCountryCacheTimestamps.entrySet().removeIf(e -> {
+ if (e.getValue() < cutoff) {
+ ipCountryCache.remove(e.getKey());
+ return true;
+ }
+ return false;
+ });
+ }
+
+ public static class GeoBlockResult {
+ private final boolean allowed;
+ private final boolean blocked;
+ private final String reason;
+
+ private GeoBlockResult(boolean allowed, boolean blocked, String reason) {
+ this.allowed = allowed;
+ this.blocked = blocked;
+ this.reason = reason;
+ }
+
+ public static GeoBlockResult allowed() {
+ return new GeoBlockResult(true, false, null);
+ }
+
+ public static GeoBlockResult blocked(String reason) {
+ return new GeoBlockResult(false, true, reason);
+ }
+
+ public boolean isAllowed() { return allowed; }
+ public boolean isBlocked() { return blocked; }
+ public String getReason() { return reason; }
+ }
+}
diff --git a/src/main/java/com/aluer/security/IncidentResponseService.java b/src/main/java/com/aluer/security/IncidentResponseService.java
index 9f186d2..d296aef 100644
--- a/src/main/java/com/aluer/security/IncidentResponseService.java
+++ b/src/main/java/com/aluer/security/IncidentResponseService.java
@@ -1,5 +1,6 @@
package com.aluer.security;
+import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service;
import java.time.Instant;
@@ -10,6 +11,8 @@ import java.util.concurrent.atomic.AtomicLong;
@Service
public class IncidentResponseService {
+ private final ServerGuardConfig config;
+
private final Map activeIncidents = new ConcurrentHashMap<>();
private final Map> actionLog = new ConcurrentHashMap<>();
private final AtomicLong totalIncidents = new AtomicLong(0);
@@ -60,7 +63,21 @@ public class IncidentResponseService {
), IncidentSeverity.CRITICAL));
}
+ public IncidentResponseService() {
+ this(new ServerGuardConfig());
+ }
+
+ public IncidentResponseService(ServerGuardConfig config) {
+ this.config = config;
+ }
+
public Incident declareIncident(String type, String description, String source, IncidentSeverity severity) {
+ if (!config.getSecurity().getSuperEvolution().isIncidentResponse()) {
+ Incident incident = new Incident("DISABLED", type, "incident-response-module-disabled", source, severity, null, Instant.now());
+ incident.resolve("incident-response-module-disabled");
+ return incident;
+ }
+
String incidentId = "INC-" + Instant.now().getEpochSecond() + "-" + randomHex(4);
ResponsePlaybook playbook = PLAYBOOKS.getOrDefault(type, null);
Incident incident = new Incident(incidentId, type, description, source, severity, playbook, Instant.now());
diff --git a/src/main/java/com/aluer/security/JwtAuthService.java b/src/main/java/com/aluer/security/JwtAuthService.java
index 2325a96..34ec3d0 100644
--- a/src/main/java/com/aluer/security/JwtAuthService.java
+++ b/src/main/java/com/aluer/security/JwtAuthService.java
@@ -1,5 +1,7 @@
package com.aluer.security;
+import com.aluer.config.ServerGuardConfig;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.crypto.Mac;
@@ -17,12 +19,19 @@ public class JwtAuthService {
private static final long DEFAULT_TTL_SECONDS = 3600;
private static final long MAX_TTL_SECONDS = 86400;
+ private final ServerGuardConfig config;
private final String secret;
private final Map tokenStore = new ConcurrentHashMap<>();
private final Map> userTokens = new ConcurrentHashMap<>();
private final Map revokedTokens = new ConcurrentHashMap<>();
public JwtAuthService() {
+ this(new ServerGuardConfig());
+ }
+
+ @Autowired
+ public JwtAuthService(ServerGuardConfig config) {
+ this.config = config;
byte[] keyBytes = new byte[64];
SECURE_RANDOM.nextBytes(keyBytes);
this.secret = Base64.getUrlEncoder().withoutPadding().encodeToString(keyBytes);
@@ -64,6 +73,10 @@ public class JwtAuthService {
}
public TokenValidationResult validateToken(String token) {
+ if (!config.getSecurity().getSuperEvolution().isJwtAuth()) {
+ return TokenValidationResult.valid("system", "jwt-auth-module-disabled", Map.of());
+ }
+
if (token == null || !token.contains(".")) {
return TokenValidationResult.invalid("Malformed token");
}
diff --git a/src/main/java/com/aluer/security/LagMachineDetectionService.java b/src/main/java/com/aluer/security/LagMachineDetectionService.java
index 19cd49f..fdecaa6 100644
--- a/src/main/java/com/aluer/security/LagMachineDetectionService.java
+++ b/src/main/java/com/aluer/security/LagMachineDetectionService.java
@@ -1,5 +1,6 @@
package com.aluer.security;
+import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service;
import java.time.Instant;
@@ -13,6 +14,8 @@ import java.util.concurrent.atomic.AtomicLong;
@Service
public class LagMachineDetectionService {
+ private final ServerGuardConfig config;
+
private final Map> lagEvents = new ConcurrentHashMap<>();
private final Map flaggedPlayers = new ConcurrentHashMap<>();
private final Map chunkActivity = new ConcurrentHashMap<>();
@@ -47,10 +50,19 @@ public class LagMachineDetectionService {
);
public LagMachineDetectionService() {
+ this(new ServerGuardConfig());
+ }
+
+ public LagMachineDetectionService(ServerGuardConfig config) {
+ this.config = config;
scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS);
}
public LagCheckResult checkBlockPlace(String playerName, String blockType, int x, int y, int z, String world) {
+ if (!config.getSecurity().getSuperEvolution().isLagMachine()) {
+ return LagCheckResult.clean();
+ }
+
String chunkKey = (x >> 4) + ":" + (z >> 4) + ":" + world;
ChunkActivity chunk = chunkActivity.computeIfAbsent(chunkKey, k -> new ChunkActivity());
chunk.recordBlockPlace(blockType, playerName);
diff --git a/src/main/java/com/aluer/security/MemoryProtectionService.java b/src/main/java/com/aluer/security/MemoryProtectionService.java
index 0ad2a71..68d2c38 100644
--- a/src/main/java/com/aluer/security/MemoryProtectionService.java
+++ b/src/main/java/com/aluer/security/MemoryProtectionService.java
@@ -1,5 +1,6 @@
package com.aluer.security;
+import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service;
import java.lang.management.ManagementFactory;
@@ -16,6 +17,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Service
public class MemoryProtectionService {
+ private final ServerGuardConfig config;
private final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final Map> alerts = new ConcurrentHashMap<>();
@@ -30,15 +32,25 @@ public class MemoryProtectionService {
private long lastGcTime;
public MemoryProtectionService() {
+ this(new ServerGuardConfig());
+ }
+
+ public MemoryProtectionService(ServerGuardConfig config) {
+ this.config = config;
lastGcCount = ManagementFactory.getGarbageCollectorMXBeans().stream()
.mapToLong(gc -> gc.getCollectionCount()).sum();
lastGcTime = ManagementFactory.getGarbageCollectorMXBeans().stream()
.mapToLong(gc -> gc.getCollectionTime()).sum();
- scheduler.scheduleAtFixedRate(this::checkMemory, MEMORY_CHECK_INTERVAL_SECONDS,
- MEMORY_CHECK_INTERVAL_SECONDS, TimeUnit.SECONDS);
+ if (config.getSecurity().getSuperEvolution().isMemoryProtection()) {
+ scheduler.scheduleAtFixedRate(this::checkMemory, MEMORY_CHECK_INTERVAL_SECONDS,
+ MEMORY_CHECK_INTERVAL_SECONDS, TimeUnit.SECONDS);
+ }
}
public MemoryCheckResult checkMemory() {
+ if (!config.getSecurity().getSuperEvolution().isMemoryProtection()) {
+ return new MemoryCheckResult(0, 0, 0, 0, 0, 0, List.of());
+ }
List warnings = new ArrayList<>();
MemoryUsage heap = memoryMXBean.getHeapMemoryUsage();
diff --git a/src/main/java/com/aluer/security/PlayerSessionValidationService.java b/src/main/java/com/aluer/security/PlayerSessionValidationService.java
new file mode 100644
index 0000000..682b46e
--- /dev/null
+++ b/src/main/java/com/aluer/security/PlayerSessionValidationService.java
@@ -0,0 +1,240 @@
+package com.aluer.security;
+
+import com.aluer.config.ServerGuardConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+
+@Service
+public class PlayerSessionValidationService {
+
+ private final ServerGuardConfig config;
+
+ private final Map activeSessions = new ConcurrentHashMap<>();
+ private final Map> uuidToIPs = new ConcurrentHashMap<>();
+ private final Map> ipToUUIDs = new ConcurrentHashMap<>();
+ private final Map ipLastSwitch = new ConcurrentHashMap<>();
+ private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+
+ private final AtomicLong totalValidations = new AtomicLong(0);
+ private final AtomicLong totalSuspicious = new AtomicLong(0);
+ private final AtomicLong totalInvalid = new AtomicLong(0);
+
+ private static final String UUID_REGEX = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$";
+ private static final String OFFLINE_UUID_PREFIX = "00000000-0000-";
+ private static final String PLAYER_NAME_REGEX = "^[a-zA-Z0-9_]{3,16}$";
+ private static final long RAPID_SWITCH_WINDOW_MS = 30000;
+ private static final int RAPID_SWITCH_THRESHOLD = 3;
+ private static final long SESSION_CLEANUP_INTERVAL_MS = 600000;
+
+ public PlayerSessionValidationService() {
+ this(new ServerGuardConfig());
+ }
+
+ @Autowired
+ public PlayerSessionValidationService(ServerGuardConfig config) {
+ this.config = config;
+ scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS);
+ }
+
+ public SessionValidationResult validateJoin(String uuid, String playerName, String ip, String sessionId) {
+ totalValidations.incrementAndGet();
+
+ if (!config.getSecurity().getSuperEvolution().isSessionValidation()) {
+ return SessionValidationResult.valid();
+ }
+
+ List reasons = new ArrayList<>();
+ boolean suspicious = false;
+
+ // Check 1: UUID format validation
+ if (uuid == null || !uuid.matches(UUID_REGEX)) {
+ totalInvalid.incrementAndGet();
+ reasons.add("INVALID_UUID_FORMAT: " + uuid);
+ return SessionValidationResult.invalid(reasons);
+ }
+
+ // Check 2: Offline-mode player detection
+ if (uuid.startsWith(OFFLINE_UUID_PREFIX)) {
+ suspicious = true;
+ reasons.add("OFFLINE_MODE_UUID: Player likely using cracked/offline mode");
+ }
+
+ // Check 3: Premium/cracked detection patterns
+ if (isCrackedPatternUUID(uuid)) {
+ suspicious = true;
+ reasons.add("CRACKED_UUID_PATTERN: UUID matches known cracked client patterns");
+ }
+
+ // Check 4: Player name regex validation
+ if (playerName == null || !playerName.matches(PLAYER_NAME_REGEX)) {
+ totalInvalid.incrementAndGet();
+ reasons.add("INVALID_PLAYER_NAME: " + playerName);
+ return SessionValidationResult.invalid(reasons);
+ }
+
+ // Check 5: Duplicate UUID detection (same UUID from different IPs)
+ Set ips = uuidToIPs.computeIfAbsent(uuid, k -> ConcurrentHashMap.newKeySet());
+ if (!ips.isEmpty() && !ips.contains(ip)) {
+ suspicious = true;
+ reasons.add("DUPLICATE_UUID: UUID " + uuid + " seen from multiple IPs: " + ips.size());
+ }
+ ips.add(ip);
+
+ // Check 6: Rapid account switching detection (same IP, different accounts)
+ Set uuids = ipToUUIDs.computeIfAbsent(ip, k -> ConcurrentHashMap.newKeySet());
+ uuids.add(uuid);
+ long now = System.currentTimeMillis();
+ Long lastSwitch = ipLastSwitch.get(ip);
+ if (lastSwitch != null && (now - lastSwitch) < RAPID_SWITCH_WINDOW_MS) {
+ if (uuids.size() >= RAPID_SWITCH_THRESHOLD) {
+ suspicious = true;
+ reasons.add("RAPID_ACCOUNT_SWITCH: " + uuids.size() + " accounts from IP " + ip + " in " + RAPID_SWITCH_WINDOW_MS + "ms");
+ }
+ }
+ ipLastSwitch.put(ip, now);
+
+ // Check 7: Session replay attack detection (same session ID reused)
+ SessionRecord existing = activeSessions.get(sessionId);
+ if (existing != null) {
+ if (!existing.uuid.equals(uuid) || !existing.ip.equals(ip)) {
+ totalSuspicious.incrementAndGet();
+ reasons.add("SESSION_REPLAY: Session ID " + sessionId + " reused by different player/IP");
+ return SessionValidationResult.suspicious(reasons);
+ }
+ }
+
+ // Store session
+ SessionRecord record = new SessionRecord(uuid, playerName, ip, sessionId, now);
+ activeSessions.put(sessionId, record);
+
+ if (suspicious) {
+ totalSuspicious.incrementAndGet();
+ return SessionValidationResult.suspicious(reasons);
+ }
+
+ return SessionValidationResult.valid();
+ }
+
+ public void invalidateSession(String sessionId) {
+ activeSessions.remove(sessionId);
+ }
+
+ public SessionRecord getSession(String sessionId) {
+ return activeSessions.get(sessionId);
+ }
+
+ public Set getIPsForUUID(String uuid) {
+ Set ips = uuidToIPs.get(uuid);
+ return ips != null ? new HashSet<>(ips) : Collections.emptySet();
+ }
+
+ public Set getUUIDsForIP(String ip) {
+ Set uuids = ipToUUIDs.get(ip);
+ return uuids != null ? new HashSet<>(uuids) : Collections.emptySet();
+ }
+
+ public Map getStatus() {
+ Map status = new LinkedHashMap<>();
+ status.put("enabled", config.getSecurity().getSuperEvolution().isSessionValidation());
+ status.put("activeSessions", activeSessions.size());
+ status.put("trackedUUIDs", uuidToIPs.size());
+ status.put("trackedIPs", ipToUUIDs.size());
+ status.put("totalValidations", totalValidations.get());
+ status.put("totalSuspicious", totalSuspicious.get());
+ status.put("totalInvalid", totalInvalid.get());
+
+ List