lock( 'vulnerability_feed_state', 60 ); if ( ! $owner ) { throw new \RuntimeException( 'Vulnerability settings are changing. Wait a moment and retry.' ); } try { return $operation(); } finally { $store->unlock( 'vulnerability_feed_state', $owner ); } } public static function generation(): string { $value = Store::freshOption( self::GENERATION, '' ); if ( ! is_string( $value ) || ( '' !== $value && ! preg_match( '/^[a-f0-9]{32}$/D', $value ) ) ) { throw new \RuntimeException( 'Feed permission history cannot be read. Reconnect the feed.' ); } return $value; } private static function advanceGeneration(): void { Store::saveOption( self::GENERATION, bin2hex( random_bytes( 16 ) ) ); } /** Mutate fresh settings under the same boundary used by feed publication. */ public static function saveSettings( callable $mutator ): array { return self::withLock( static function () use ( $mutator ): array { $current = Store::freshOption( 'antivirus_ai_settings', array() ); if ( ! is_array( $current ) ) { throw new \RuntimeException( 'Current settings cannot be read.' ); } $next = $mutator( $current ); if ( ! is_array( $next ) ) { throw new \RuntimeException( 'Invalid protection settings change.' ); } if ( (bool) ( $current['vulnerability_feed'] ?? false ) !== (bool) ( $next['vulnerability_feed'] ?? false ) ) { self::advanceGeneration(); } Store::saveOption( 'antivirus_ai_settings', $next ); return $next; } ); } public static function key(): string { $environment = getenv( 'ANTIVIRUS_AI_WORDFENCE_KEY' ); if ( is_string( $environment ) && '' !== $environment ) { return $environment; } $record = Store::freshOption( self::OPTION, array() ); if ( ! is_array( $record ) || ! isset( $record['cipher'] ) ) { return ''; } if ( ! function_exists( 'sodium_crypto_secretbox_open' ) ) { throw new \RuntimeException( 'Encrypted feed access requires the PHP sodium extension.' ); } $bytes = base64_decode( (string) $record['cipher'], true ); if ( false === $bytes || strlen( $bytes ) < 41 || strlen( $bytes ) > 1024 ) { throw new \RuntimeException( 'Saved feed access cannot be read. Reconnect the feed.' ); } $plain = sodium_crypto_secretbox_open( substr( $bytes, 24 ), substr( $bytes, 0, 24 ), self::encryptionKey() ); if ( false === $plain || ! self::valid( $plain ) ) { throw new \RuntimeException( 'Saved feed access cannot be decrypted after a site-key change. Reconnect the feed.' ); } return $plain; } private static function valid( string $key ): bool { return (bool) preg_match( '/^[\x21-\x7e]{8,512}$/D', $key ); } private static function encryptionKey(): string { return hash( 'sha256', 'antivirus.ai/vulnerability-feed/v1|' . wp_salt( 'auth' ), true ); } public static function save( string $key ): void { $environment = getenv( 'ANTIVIRUS_AI_WORDFENCE_KEY' ); if ( is_string( $environment ) && '' !== $environment ) { throw new \RuntimeException( 'Feed access is managed by your host. Ask the host to replace or remove its configured token before saving one here.' ); } if ( ! self::valid( $key ) ) { throw new \InvalidArgumentException( 'Enter a valid feed token without spaces (8–512 characters).' ); } if ( ! function_exists( 'sodium_crypto_secretbox' ) ) { throw new \RuntimeException( 'The PHP sodium extension is required to store feed access securely.' ); } self::withLock( static function () use ( $key ): void { $nonce = random_bytes( SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ); self::advanceGeneration(); Store::saveOption( self::OPTION, array( 'cipher' => base64_encode( $nonce . sodium_crypto_secretbox( $key, $nonce, self::encryptionKey() ) ), 'saved' => time() ) ); self::deleteSaved( array( 'antivirus_ai_vulnerability_attempt', 'antivirus_ai_vulnerability_cache' ) ); } ); } private static function deleteSaved( array $options ): void { foreach ( $options as $option ) { delete_option( $option ); $missing = new \stdClass(); if ( Store::freshOption( $option, $missing ) !== $missing ) { throw new \RuntimeException( 'Some saved feed data could not be removed. Restore database access and retry; review the saved permission state before requesting another check.' ); } } } public static function disconnect(): void { self::withLock( static function (): void { $settings = Store::freshOption( 'antivirus_ai_settings', array() ); $settings['vulnerability_feed'] = false; // Consent is durably withdrawn before attempting optional data deletion. Store::saveOption( 'antivirus_ai_settings', $settings ); self::advanceGeneration(); self::deleteSaved( array( self::OPTION, 'antivirus_ai_vulnerability_cache', 'antivirus_ai_vulnerability_attempt' ) ); } ); } public static function remove(): void { self::disconnect(); } public static function status(): string { try { return '' !== self::key() ? 'configured' : 'not_connected'; } catch ( \Throwable $error ) { return 'reconnect_required'; } } }