{
"event": "PreToolUse",
"tool_name": "Edit",
"tool_input": {
"file_path": "\/tmp\/BotDetectionService.php",
"old_string": " public function unblockIp(string $ip): bool\n {\n \/\/ Remove from JSON file\n if (file_exists($this->blockedIpsFile)) {\n $blockedIps = json_decode(file_get_contents($this->blockedIpsFile), true) ?? [];\n unset($blockedIps[$ip]);\n file_put_contents($this->blockedIpsFile, json_encode($blockedIps, JSON_PRETTY_PRINT));\n }\n \n \/\/ Remove UFW rule\n try {\n $ufwCmd = sprintf('sudo ufw delete deny from %s', escapeshellarg($ip));\n shell_exec($ufwCmd . ' 2>&1');\n SystemLogger::info('security', 'BotDetectionService', \"UFW rule removed for IP: $ip\");\n return true;\n } catch (\\Exception $e) {\n SystemLogger::error('security', 'BotDetectionService', \"Failed to remove UFW rule for $ip: \" . $e->getMessage());\n return false;\n }\n }",
"new_string": " public function unblockIp(string $ip): bool\n {\n if (file_exists($this->blockedIpsFile)) {\n $blockedIps = json_decode(file_get_contents($this->blockedIpsFile), true) ?? [];\n unset($blockedIps[$ip]);\n file_put_contents($this->blockedIpsFile, json_encode($blockedIps, JSON_PRETTY_PRINT));\n }\n\n SystemLogger::info('security', 'BotDetectionService', \"IP unblocked: $ip\");\n return true;\n }",
"replace_all": false
}
}
{
"tool_response": {
"filePath": "\/tmp\/BotDetectionService.php",
"oldString": " public function unblockIp(string $ip): bool\n {\n \/\/ Remove from JSON file\n if (file_exists($this->blockedIpsFile)) {\n $blockedIps = json_decode(file_get_contents($this->blockedIpsFile), true) ?? [];\n unset($blockedIps[$ip]);\n file_put_contents($this->blockedIpsFile, json_encode($blockedIps, JSON_PRETTY_PRINT));\n }\n \n \/\/ Remove UFW rule\n try {\n $ufwCmd = sprintf('sudo ufw delete deny from %s', escapeshellarg($ip));\n shell_exec($ufwCmd . ' 2>&1');\n SystemLogger::info('security', 'BotDetectionService', \"UFW rule removed for IP: $ip\");\n return true;\n } catch (\\Exception $e) {\n SystemLogger::error('security', 'BotDetectionService', \"Failed to remove UFW rule for $ip: \" . $e->getMessage());\n return false;\n }\n }",
"newString": " public function unblockIp(string $ip): bool\n {\n if (file_exists($this->blockedIpsFile)) {\n $blockedIps = json_decode(file_get_contents($this->blockedIpsFile), true) ?? [];\n unset($blockedIps[$ip]);\n file_put_contents($this->blockedIpsFile, json_encode($blockedIps, JSON_PRETTY_PRINT));\n }\n\n SystemLogger::info('security', 'BotDetectionService', \"IP unblocked: $ip\");\n return true;\n }",
"originalFile": "<?php\n\ndeclare(strict_types=1);\n\nnamespace App\\Services;\n\n\nuse App\\Services\\SystemLogger;\nuse App\\Helpers\\ConfigLoader;\nuse App\\Services\\DatabaseManager;\n\n\/**\n * Bot Detection Service mit fail2ban\/nftables-Integration\n * Blockiert unerwünschte Bots basierend auf MariaDB-Konfiguration\n *\/\nclass BotDetectionService\n{\n private array $config;\n private string $blockedIpsFile;\n private array $requestCounts = [];\n private RateLimitingService $rateLimiter;\n private ?GeoIPService $geoIPService = null;\n private ?UserAgentParser $userAgentParser = null;\n\n\n \/**\n * DNS verification domains for search engine bots\n * Pattern => array of valid domain suffixes\n *\/\n private const DNS_VERIFY_DOMAINS = [\n 'Googlebot' => ['googlebot.com', 'google.com'],\n 'FeedFetcher-Google' => ['google.com'],\n 'Google-Read-Aloud' => ['google.com', 'googleusercontent.com'],\n 'Google-InspectionTool' => ['google.com'],\n 'Bingbot' => ['search.msn.com'],\n 'Applebot' => ['applebot.apple.com'],\n 'DuckDuckBot' => ['duckduckgo.com'],\n 'Yandex' => ['yandex.ru', 'yandex.net', 'yandex.com'],\n ];\n\n\n private const FAIL2BAN_LOG = '\/var\/log\/apache2\/bot-blocks.log';\n\n public function __construct()\n {\n $configLoader = ConfigLoader::getInstance();\n \n \/\/ Load ALL configuration directly from MariaDB - NO FALLBACKS\n $this->config = [\n 'settings' => [\n 'enabled' => (bool)$configLoader->get('bot.enabled'),\n 'log_blocked_bots' => (bool)$configLoader->get('bot.log_blocked_bots'),\n 'block_duration_minutes' => (int)$configLoader->get('bot.block_duration_minutes'),\n 'max_requests_per_minute' => (int)$configLoader->get('bot.max_requests_per_minute')\n ],\n 'logging' => [\n 'blocked_attempts' => (bool)$configLoader->get('bot.logging.blocked_attempts'),\n 'log_file' => $configLoader->get('bot.logging.log_file')\n ],\n 'rate_limiting' => [\n 'enabled' => (bool)$configLoader->get('bot.rate_limiting.enabled')\n ],\n 'actions' => [\n 'block_request' => (bool)$configLoader->get('bot.actions.block_request'),\n 'return_403' => (bool)$configLoader->get('bot.actions.return_403'),\n 'return_404' => (bool)$configLoader->get('bot.actions.return_404')\n ],\n 'whitelist' => [\n 'user_agents' => $this->safeJsonDecode($configLoader->get('bot.whitelist.user_agents')),\n 'user_agent_patterns' => $this->safeJsonDecode($configLoader->get('bot.whitelist.user_agent_patterns')),\n 'ips' => $this->safeJsonDecode($configLoader->get('bot.whitelist.ips'))\n ],\n 'blacklist' => [\n 'user_agents' => $this->safeJsonDecode($configLoader->get('bot.blacklist.user_agents')),\n 'user_agent_patterns' => $this->safeJsonDecode($configLoader->get('bot.blacklist.user_agent_patterns'))\n ],\n 'geo_blocking' => $this->loadGeoBlockingConfig($configLoader),\n 'unknown_bot' => $this->loadUnknownBotConfig($configLoader)\n ];\n\n \/\/ Initialize GeoIPService for geo-blocking\n if ($this->config['geo_blocking']['enabled']) {\n try {\n $this->geoIPService = new GeoIPService();\n } catch (\\Exception $e) {\n SystemLogger::error('security', 'BotDetectionService', \"GeoIPService init failed: \" . $e->getMessage());\n }\n }\n\n \/\/ Initialize UserAgentParser for unknown bot detection\n if ($this->config['unknown_bot']['enabled']) {\n $this->userAgentParser = new UserAgentParser();\n }\n \n $basePath = dirname(__DIR__, 2); \/\/ Go up from \/src\/Services to project root\n $this->blockedIpsFile = $basePath . '\/cache\/blocked_ips.json';\n \n \/\/ Rate limiting from MariaDB - NO JSON FALLBACKS\n $rateLimitConfig = [\n 'enabled' => $this->config['rate_limiting']['enabled'],\n 'primary' => [\n 'requests_per_minute' => (int)$configLoader->get('bot.rate_limiting.primary.requests_per_minute'),\n 'burst_requests' => (int)$configLoader->get('bot.rate_limiting.primary.burst_requests'),\n 'burst_window_seconds' => (int)$configLoader->get('bot.rate_limiting.primary.burst_window_seconds')\n ],\n 'levels' => $this->safeJsonDecode($configLoader->get('bot.rate_limiting.levels')),\n 'tolerance_multipliers' => $this->safeJsonDecode($configLoader->get('bot.rate_limiting.tolerance_multipliers'))\n ];\n \n $this->rateLimiter = new RateLimitingService($rateLimitConfig);\n \n \/\/ Lade bestehende blockierte IPs\n $this->loadBlockedIps();\n }\n \n \/**\n * Hauptfunktion: Prüft ob Request blockiert werden soll\n *\/\n public function shouldBlockRequest(): bool\n {\n if (!$this->config['settings']['enabled']) {\n return false;\n }\n \n $clientIp = $this->getClientIp();\n $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';\n \n \/\/ 1. Whitelist-Prüfung (höchste Priorität)\n if ($this->isWhitelisted($clientIp, $userAgent)) {\n $this->logIfEnabled(\"Whitelist match for IP: $clientIp, UA: $userAgent\", 'whitelist_matches');\n return false;\n }\n\n \/\/ 2. Geo-Blocking (vor Blacklist, nach Whitelist)\n $geoBlockResult = $this->checkGeoBlocking($clientIp);\n if ($geoBlockResult['blocked']) {\n $this->handleGeoBlock($clientIp, $geoBlockResult);\n return true;\n }\n\n \/\/ 3. Unknown Bot Check (vor Blacklist, nach Geo)\n $unknownBotResult = $this->checkUnknownBot($userAgent);\n if ($unknownBotResult['blocked']) {\n $this->handleUnknownBotBlock($clientIp, $userAgent, $unknownBotResult);\n return true;\n }\n\n \/\/ 4. Blacklist-Prüfung\n if ($this->isBlacklisted($clientIp, $userAgent)) {\n $this->blockRequest($clientIp, $userAgent, 'blacklist_match');\n return true;\n }\n \n \/\/ 5. Advanced Rate Limiting\n $isAuthenticated = $this->isUserAuthenticated();\n $rateLimitResult = $this->rateLimiter->isRateLimited($clientIp, $userAgent, $isAuthenticated);\n \n if ($rateLimitResult['limited']) {\n $this->handleRateLimitViolation($clientIp, $userAgent, $rateLimitResult);\n return true;\n }\n \n return false;\n }\n \n \/**\n * Whitelist-Prüfung\n *\/\n private function isWhitelisted(string $ip, string $userAgent): bool\n {\n $whitelist = $this->config['whitelist'];\n\n \/\/ Exakte User-Agent Matches\n if (in_array($userAgent, $whitelist['user_agents'] ?? [])) {\n return true;\n }\n\n \/\/ User-Agent Pattern Matches (with DNS verification for search engines)\n foreach ($whitelist['user_agent_patterns'] ?? [] as $pattern) {\n if (preg_match('\/' . $pattern . '\/i', $userAgent)) {\n \/\/ For search engine bots: require DNS verification\n if (isset(self::DNS_VERIFY_DOMAINS[$pattern])) {\n if ($this->verifySearchEngineDNS($ip, $pattern)) {\n return true;\n }\n \/\/ DNS verification failed - don't whitelist, continue checking\n continue;\n }\n return true;\n }\n }\n\n \/\/ IP-Ranges\n foreach ($whitelist['ips'] ?? [] as $range) {\n if ($this->ipInRange($ip, $range)) {\n return true;\n }\n }\n\n \/\/ Check tracking_whitelist (bleib-mensch verified users)\n if ($this->isInTrackingWhitelist($ip)) {\n return true;\n }\n\n return false;\n }\n\n \/**\n * Prüft ob IP in tracking_whitelist (bleib-mensch Challenge bestanden)\n *\/\n private function isInTrackingWhitelist(string $ip): bool\n {\n try {\n $pdo = DatabaseManager::getConnection('mariadb');\n $ipHash = hash('sha256', $ip);\n\n $stmt = $pdo->prepare(\"\n SELECT 1 FROM tracking_whitelist\n WHERE ip_hash = :ip_hash\n AND expires_at > NOW()\n LIMIT 1\n \");\n $stmt->execute([':ip_hash' => $ipHash]);\n\n return $stmt->fetchColumn() !== false;\n } catch (\\Exception $e) {\n SystemLogger::error('security', 'BotDetectionService', \"tracking_whitelist check failed: \" . $e->getMessage());\n return false;\n }\n }\n\n\n\n \/**\n * Verify search engine bot via reverse\/forward DNS\n *\n * Process:\n * 1. Reverse DNS: IP → hostname\n * 2. Check if hostname ends with valid domain for this bot\n * 3. Forward DNS: hostname → IP (must match original)\n *\n * @param string $ip Client IP address\n * @param string $pattern Bot pattern that matched (e.g., \"Googlebot\")\n * @return bool True if DNS verification passed\n *\/\n private function verifySearchEngineDNS(string $ip, string $pattern): bool\n {\n \/\/ Only verify patterns that have DNS domains configured\n if (!isset(self::DNS_VERIFY_DOMAINS[$pattern])) {\n return true; \/\/ No DNS verification required for this pattern\n }\n\n \/\/ Perform DNS verification\n $verified = $this->performDNSVerification($ip, $pattern);\n\n \/\/ Log verification result\n $status = $verified ? 'PASS' : 'FAIL';\n SystemLogger::info('security', 'BotDetectionService', \"DNS verification {$status}: IP={$ip}, Pattern={$pattern}\");\n\n return $verified;\n }\n\n \/**\n * Perform actual DNS verification (reverse + forward lookup)\n *\/\n privat... [TRUNCATED-cd9a7abaf41bee0d]",
"structuredPatch": [
{
"oldStart": 729,
"oldLines": 23,
"newStart": 729,
"newLines": 14,
"lines": [
" ",
" public function unblockIp(string $ip): bool",
" {",
"- \/\/ Remove from JSON file",
" if (file_exists($this->blockedIpsFile)) {",
" $blockedIps = json_decode(file_get_contents($this->blockedIpsFile), true) ?? [];",
" unset($blockedIps[$ip]);",
" file_put_contents($this->blockedIpsFile, json_encode($blockedIps, JSON_PRETTY_PRINT));",
" }",
"- ",
"- \/\/ Remove UFW rule",
"- try {",
"- $ufwCmd = sprintf('sudo ufw delete deny from %s', escapeshellarg($ip));",
"- shell_exec($ufwCmd . ' 2>&1');",
"- SystemLogger::info('security', 'BotDetectionService', \"UFW rule removed for IP: $ip\");",
"- return true;",
"- } catch (\\Exception $e) {",
"- SystemLogger::error('security', 'BotDetectionService', \"Failed to remove UFW rule for $ip: \" . $e->getMessage());",
"- return false;",
"- }",
"+",
"+ SystemLogger::info('security', 'BotDetectionService', \"IP unblocked: $ip\");",
"+ return true;",
" }",
" ",
" public function getConfig(): array"
]
}
],
"userModified": false,
"replaceAll": false
}
}