controller instanceof PageNotFoundController)) { return true; } // 2. Safely check if the Cache module is installed and enabled if (!Module::isInstalled('dbmemorycache') || !Module::isEnabled('dbmemorycache')) { return true; // Pass gracefully if the module is missing or disabled } /** @var DbMemoryCache $cache */ $cache = Module::getInstanceByName('dbmemorycache'); if (!$cache) { return true; } // 3. Generate a unique hash for this specific IP's 404 traffic $cacheKey = hash('sha256', '404_spam_limiter_' . $ip); // 4. Lookup their current 404 hit count in the memory table $currentCount = 0; if ($cache->existsValue($cacheKey)) { $currentCount = (int) $cache->getValue($cacheKey); } // 5. Increment their strike counter $currentCount++; // 6. Have they triggered too many 404s? if ($currentCount > self::MAX_404_REQUESTS) { // Log it so Fail2ban can block them at the server firewall level BotLogger::logBan($ip, '404_RATE_LIMIT_EXCEEDED'); // Drop the connection instantly to save server resources header('HTTP/1.1 429 Too Many Requests'); die('429 Too Many Requests - Stop Scanning'); } // 7. Save the new strike count back to the cache // Note: Because we overwrite it here, this creates a "Sliding Window". // If a bot keeps spamming, the 300s timer resets every time, keeping them trapped! $cache->setValue($cacheKey, $currentCount, self::TIME_WINDOW); return true; } }