false, 'generation' => '', 'attempt' => '', 'result' => 'disabled', 'next_attempt' => 0, 'last_success' => 0, 'published' => 0, 'ips' => array(), 'digest' => '', 'checked' => 0 ); } private static function locked( callable $work ): mixed { $store = new Store(); $owner = $store->lock( 'reputation-feed', 45 ); if ( ! $owner ) { throw new \RuntimeException( 'The reputation preference is changing. Retry shortly.' ); } try { return $work(); } finally { $store->unlock( 'reputation-feed', $owner ); } } public static function setEnabled( bool $enabled ): void { self::locked( static function () use ( $enabled ): void { try { $state = self::state(); } catch ( \Throwable $error ) { $state = array( 'next_attempt' => time() + 3600 ); } $state = array_merge( $state, array( 'enabled' => $enabled, 'generation' => bin2hex( random_bytes( 16 ) ), 'attempt' => '', 'result' => $enabled ? 'awaiting_download' : 'disabled' ) ); // Withdrawal erases the operational feed immediately and fences any paused response. if ( ! $enabled ) { $state['ips'] = array(); $state['published'] = 0; $state['digest'] = ''; } Store::saveOption( self::OPTION, $state ); } ); self::schedule(); } public static function register(): void { add_action( self::HOOK, array( self::class, 'tick' ) ); add_action( 'init', array( self::class, 'guard' ), -120 ); self::schedule(); } private static function schedule(): void { try { $state = self::state(); if ( true !== $state['enabled'] ) { self::stop(); return; } if ( ! wp_next_scheduled( self::HOOK ) ) { wp_schedule_event( time() + 60, 'hourly', self::HOOK ); } } catch ( \Throwable $error ) {} } public static function stop(): void { wp_clear_scheduled_hook( self::HOOK ); } public static function tick(): void { try { self::refresh(); } catch ( \Throwable $error ) {} } /** Strict authoritative whole-list parser. Rejected/truncated data never replaces a prior snapshot. */ public static function parse( string $body, string $lastModified, ?int $now = null ): array { $now = $now ?? time(); if ( strlen( $body ) > self::CAP || '' === $body || ! str_ends_with( $body, "\n" ) || preg_match( '/[^0-9.\r\n]/', $body ) ) { throw new \RuntimeException( 'The compromised-IP response is incomplete or malformed.' ); } $published = \DateTimeImmutable::createFromFormat( '!D, d M Y H:i:s \G\M\T', $lastModified, new \DateTimeZone( 'UTC' ) ); $errors = \DateTimeImmutable::getLastErrors(); if ( ! $published || ( is_array( $errors ) && ( $errors['warning_count'] || $errors['error_count'] ) ) || $published->format( 'D, d M Y H:i:s \G\M\T' ) !== $lastModified || $published->getTimestamp() > $now + 300 || $published->getTimestamp() < $now - 172800 ) { throw new \RuntimeException( 'The provider did not supply a recent, valid publication timestamp.' ); } $ips = array(); foreach ( explode( "\n", trim( $body ) ) as $line ) { $ip = rtrim( $line, "\r" ); if ( ! filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) || ! PinnedHttp::publicIp( $ip ) || isset( $ips[ $ip ] ) || count( $ips ) >= 10000 ) { throw new \RuntimeException( 'The provider list contains an invalid, duplicate or excessive entry.' ); } $ips[ $ip ] = true; } if ( count( $ips ) < 1 ) { throw new \RuntimeException( 'An empty feed needs provider review; it cannot silently clear the list.' ); } return array( 'ips' => $ips, 'published' => $published->getTimestamp(), 'digest' => hash( 'sha256', $body ) ); } public static function refresh(): array { $start = self::locked( static function (): ?array { $state = self::state(); if ( true !== $state['enabled'] || (int) $state['next_attempt'] > time() ) { return null; } $state['attempt'] = bin2hex( random_bytes( 16 ) ); $state['next_attempt'] = time() + 3600; $state['checked'] = time(); $state['result'] = 'checking'; Store::saveOption( self::OPTION, $state ); return $state; } ); if ( null === $start ) { return self::status(); } $packet = null; $result = 'unavailable'; try { $response = wp_safe_remote_get( self::URL, array( 'timeout' => 12, 'redirection' => 0, 'sslverify' => true, 'reject_unsafe_urls' => true, 'limit_response_size' => self::CAP + 1, 'cookies' => array(), 'user-agent' => 'antivirus.ai-reputation/1', 'headers' => array( 'Accept' => 'text/plain' ) ) ); if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { throw new \RuntimeException( 'The provider is unavailable.' ); } $result = 'rejected'; $body = wp_remote_retrieve_body( $response ); if ( ! is_string( $body ) ) { throw new \RuntimeException( 'The provider returned no list.' ); } $packet = self::parse( $body, (string) wp_remote_retrieve_header( $response, 'last-modified' ) ); } catch ( \Throwable $error ) {} self::locked( static function () use ( $packet, $result, $start ): void { $current = self::state(); if ( true !== $current['enabled'] || $current['generation'] !== $start['generation'] || $current['attempt'] !== $start['attempt'] ) { return; } if ( $packet && ( $packet['published'] < (int) $current['published'] || ( $packet['published'] === (int) $current['published'] && '' !== $current['digest'] && $packet['digest'] !== $current['digest'] ) ) ) { $packet = null; $result = 'rejected'; } if ( $packet ) { $current = array_merge( $current, $packet ); $result = 'current'; $current['last_success'] = time(); } $current['result'] = $result; $current['attempt'] = ''; $current['next_attempt'] = time() + ( 'current' === $result ? 43200 : 3600 ); Store::saveOption( self::OPTION, $current ); } ); return self::status(); } public static function fresh( array $state, ?int $now = null ): bool { $now = $now ?? time(); return true === ( $state['enabled'] ?? false ) && is_int( $state['published'] ?? null ) && $state['published'] >= $now - 172800 && $state['published'] <= $now + 300 && is_int( $state['last_success'] ?? null ) && $state['last_success'] >= $now - 86400 && $state['last_success'] <= $now + 300 && is_array( $state['ips'] ?? null ) && count( $state['ips'] ) <= 10000; } public static function status(): array { try { $state = self::state(); } catch ( \Throwable $error ) { $state = array( 'enabled' => false, 'result' => 'state_unavailable' ); } $enabled = true === ( $state['enabled'] ?? false ); return array( 'enabled' => $enabled, 'status' => $enabled && ! self::fresh( $state ) && ! empty( $state['last_success'] ) ? 'stale_not_enforced' : ( $state['result'] ?? 'disabled' ), 'count' => count( is_array( $state['ips'] ?? null ) ? $state['ips'] : array() ), 'last_check' => (int) ( $state['checked'] ?? 0 ), 'last_success' => (int) ( $state['last_success'] ?? 0 ), 'published' => (int) ( $state['published'] ?? 0 ), 'next_attempt' => (int) ( $state['next_attempt'] ?? 0 ), 'destination' => self::URL, 'provider' => 'Emerging Threats compromised IPs', 'license' => 'GPL-2.0', 'license_source' => self::TERMS, 'privacy_url' => 'https://www.proofpoint.com/us/legal/privacy-policy', 'limitation' => 'Downloads the provider’s compromised IPv4 list every 12 hours after separate permission. The provider receives the server IP and normal HTTPS metadata; no visitor IP, site URL, inventory, finding or file is uploaded. Local source lookup uses REMOTE_ADDR, or the existing explicitly trusted-proxy chain. Stale data is not enforced. This is provider-reported IP reputation, not comprehensive bot identification, a CDN firewall or DDoS mitigation. Disable erases the operational list and rejects in-flight results.' ); } public static function matches( array $state, ?string $address ): bool { return null !== $address && self::fresh( $state ) && true === ( $state['ips'][ $address ] ?? false ); } public static function guard(): void { if ( ( defined( 'WP_CLI' ) && WP_CLI ) || ( defined( 'DOING_CRON' ) && DOING_CRON ) || ( defined( 'ANTIVIRUS_AI_DISABLE_FIREWALL' ) && ANTIVIRUS_AI_DISABLE_FIREWALL ) ) { return; } $config = Firewall::configuration(); if ( 'off' === $config['mode'] ) { return; } try { $match = self::matches( self::state(), LoginLimiter::clientAddress( $_SERVER ) ); } catch ( \Throwable $error ) { return; } if ( ! $match ) { return; } $rule = array( 'rule' => 'reputation.compromised-source', 'parameter' => '@source' ); $route = Firewall::route( Firewall::requestUri() ); $method = Firewall::requestMethod(); $excepted = Firewall::excepted( $rule, $route, $method, $config['exceptions'] ); Firewall::record( $rule['rule'], $excepted ? 'excepted' : $config['mode'], $method, $route, '@source' ); if ( ! $excepted && 'block' === $config['mode'] ) { nocache_headers(); wp_die( esc_html__( 'The connection source is temporarily restricted by site protection. Contact the site owner if you need help.', 'shieldlume-security' ), esc_html__( 'Request blocked', 'shieldlume-security' ), array( 'response' => 403 ) ); } } }