<?php if (!defined('DASHBOARD_ROOT')) { http_response_code(403); exit; } class CircuitBreaker { const LAYER_NORMAL = 'normal'; const LAYER_SLOW = 'slow'; const LAYER_STALLED = 'stalled'; const LAYER_POISONED = 'poisoned'; const LAYER_BLACKHOLE = 'blackhole'; private const THRESHOLDS = [ self::LAYER_NORMAL => [0, 20], self::LAYER_SLOW => [21, 40], self::LAYER_STALLED => [41, 60], self::LAYER_POISONED => [61, 80], self::LAYER_BLACKHOLE => [81, 100], ]; public static function evaluate(int $threatScore): string { foreach (self::THRESHOLDS as $layer => $range) { if ($threatScore >= $range[0] && $threatScore <= $range[1]) { return $layer; } } return self::LAYER_BLACKHOLE; } public static function apply(string $layer): bool { if ($layer === self::LAYER_SLOW || $layer === self::LAYER_STALLED) { $ip = $_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; $lockFile = sys_get_temp_dir() . '/cb_' . md5($ip); $current = (int)@file_get_contents($lockFile); if ($current > 15) { self::logBlackhole(); return false; } @file_put_contents($lockFile, $current + 1); register_shutdown_function(function() use ($lockFile) { $c = (int)@file_get_contents($lockFile); if ($c > 0) @file_put_contents($lockFile, $c - 1); }); } switch ($layer) { case self::LAYER_NORMAL: return true; case self::LAYER_SLOW: usleep(2000000); return true; case self::LAYER_STALLED: usleep(5000000); $_SESSION['_require_captcha'] = true; return true; case self::LAYER_POISONED: $_SESSION['_poisoned'] = true; $_SESSION['_poison_start'] = $_SESSION['_poison_start'] ?? time(); if ((time() - $_SESSION['_poison_start']) > 1800) { self::logEscalation(); return false; } return true; case self::LAYER_BLACKHOLE: self::logBlackhole(); return false; default: return true; } } public static function isPoisoned(): bool { return !empty($_SESSION['_poisoned']); } public static function getIPLayer(string $ip): array { $score = 0; try { $row = Database::fetchOne( "SELECT threat_score FROM sec_visitor_sessions WHERE ip_address = :ip ORDER BY created_at DESC LIMIT 1", [':ip' => $ip] ); if ($row) $score = (int)$row['threat_score']; } catch (\Throwable $e) {} $layer = self::evaluate($score); return [ 'ip' => $ip, 'score' => $score, 'layer' => $layer, 'label' => strtoupper($layer), ]; } public static function layerSummary(int $hours = 24): array { $summary = [ self::LAYER_NORMAL => 0, self::LAYER_SLOW => 0, self::LAYER_STALLED => 0, self::LAYER_POISONED => 0, self::LAYER_BLACKHOLE => 0, ]; try { $rows = Database::fetchAll( "SELECT threat_score FROM sec_visitor_sessions                  WHERE created_at > DATE_SUB(NOW(), INTERVAL :h HOUR)                  GROUP BY ip_address                  ORDER BY threat_score DESC", [':h' => $hours] ); foreach ($rows as $r) { $layer = self::evaluate((int)$r['threat_score']); $summary[$layer]++; } } catch (\Throwable $e) {} return $summary; } private static function logEscalation(): void { $ip = $_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; try { Events::record(Events::CAT_SECURITY, 'circuit.escalated', 'security', null, [ 'ip' => $ip, 'from' => self::LAYER_POISONED, 'to' => self::LAYER_BLACKHOLE, 'reason' => 'Poison timeout exceeded 30 minutes', ]); } catch (\Throwable $e) {} } private static function logBlackhole(): void { $ip = $_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; try { Events::record(Events::CAT_SECURITY, 'circuit.blackhole', 'security', null, [ 'ip' => $ip, ]); } catch (\Throwable $e) {} } } 