Published : August 6, 2026

CVE-2026-66707: Meta for WooCommerce <= 3.7.5 Unauthenticated Stored Cross-Site Scripting PoC, Patch Analysis & Rule

Severity High (CVSS 7.2)
CWE 79
Vulnerable Version 3.7.5
Patched Version 3.7.6
Disclosed July 30, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-66707: This vulnerability is an unauthenticated stored cross-site scripting (XSS) issue in the Meta for WooCommerce plugin versions up to and including 3.7.5. The flaw stems from multiple instances of insufficient input sanitization and output escaping within the plugin’s codebase. With a CVSS score of 7.2, this vulnerability allows an unauthenticated attacker to inject arbitrary web scripts into the application, which will execute whenever an administrator or other user accesses the affected page.

Root Cause: The root cause is the systematic replacement of robust sanitization functions with weaker or contextually inappropriate ones across the plugin’s code, and the lack of output escaping on several dynamically generated values. In the vulnerable version, the plugin uses functions like `wc_clean()` and `stripslashes()` for processing user-controlled data, which do not provide XSS protection. Specifically, in `includes/Handlers/Connection.php` at line 399, the `err_code` parameter is processed with `stripslashes( wc_clean() )`, which fails to neutralize malicious HTML or JavaScript. Furthermore, exception messages containing unescaped user input are passed to `ApiException` in `class-wc-facebookcommerce.php` (line 462), creating a potential for output injection if these messages are rendered by the interface. The patch also addresses a missing `defined( ‘ABSPATH’ ) || exit;` guard in `class-wc-facebookcommerce.php` and `facebook-commerce-events-tracker.php`, which prevents direct file access and mitigates a separate attack vector.

Exploitation: An unauthenticated attacker can exploit this vulnerability by sending a crafted HTTP request to the plugin’s connection callback endpoint. The endpoint processes the `err_code` GET parameter in `includes/Handlers/Connection.php` (line 396-399). By setting `err_code` to a value containing a script payload, such as `”>alert(1)`, the attacker can bypass the insufficient sanitization. This payload would then be incorporated into the connection error handling logic and stored or rendered in a way that executes in the victim’s browser when they access the affected page, such as the plugin’s settings or connection status screen. The attack does not require authentication, making it accessible to any remote user.

Patch Analysis: The patch, released in version 3.7.6, addresses the vulnerability by implementing stricter sanitization and output escaping. It replaces `wc_clean()` with `sanitize_text_field()` for string parameters and `esc_url_raw()` for URL parameters throughout the codebase, including the vulnerable `err_code` parameter in `includes/Handlers/Connection.php`. The patch also wraps exception messages with `esc_html()` to prevent any HTML or script injection through error reporting. Additionally, the patch adds `defined( ‘ABSPATH’ ) || exit;` to multiple files to prevent direct file access. These changes ensure that user-supplied data is treated as plain text or safe URLs, effectively neutralizing XSS payloads.

Impact: Successful exploitation of this vulnerability allows an unauthenticated attacker to execute arbitrary JavaScript in the context of an authenticated user’s session. The most severe impact occurs when an administrator is targeted, as the attacker could hijack the session, create rogue admin accounts, modify plugin settings, steal sensitive configuration data, or inject malicious content into the site. This could lead to complete site compromise, data theft, malware distribution to site visitors, and permanent damage to the site’s integrity and reputation.

Differential between vulnerable and patched code

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

Code Diff
--- a/facebook-for-woocommerce/class-wc-facebookcommerce.php
+++ b/facebook-for-woocommerce/class-wc-facebookcommerce.php
@@ -8,6 +8,8 @@
  * @package MetaCommerce
  */

+defined( 'ABSPATH' ) || exit;
+
 require_once __DIR__ . '/includes/fbutils.php';

 use AutomatticWooCommerceAdminFeaturesFeatures as WooAdminFeatures;
@@ -457,7 +459,7 @@
 		}
 		if ( ! is_object( $this->api ) ) {
 			if ( ! $access_token ) {
-				throw new ApiException( __( 'Cannot create the API instance because the access token is missing.', 'facebook-for-woocommerce' ) );
+				throw new ApiException( esc_html__( 'Cannot create the API instance because the access token is missing.', 'facebook-for-woocommerce' ) );
 			}
 			$this->api = new WooCommerceFacebookAPI( $access_token );
 		} else {
--- a/facebook-for-woocommerce/facebook-commerce-events-tracker.php
+++ b/facebook-for-woocommerce/facebook-commerce-events-tracker.php
@@ -15,6 +15,8 @@
 use WooCommerceFacebookFrameworkLogger;
 use WooCommerceFacebookIntegrationsCostOfGoodsCostOfGoods;

+defined( 'ABSPATH' ) || exit;
+
 if ( ! class_exists( 'WC_Facebookcommerce_EventsTracker' ) ) :

 	if ( ! class_exists( 'WC_Facebookcommerce_Utils' ) ) {
--- a/facebook-for-woocommerce/facebook-for-woocommerce.php
+++ b/facebook-for-woocommerce/facebook-for-woocommerce.php
@@ -10,14 +10,16 @@
  * Description: Grow your business on Meta platforms! Use this official plugin to help sell more of your products using Facebook and Instagram. After completing the setup, you'll be ready to create ads that promote your products and you can also create a shop section on your Page where customers can browse your products.
  * Author: Meta
  * Author URI: https://www.meta.com/
- * Version: 3.7.5
+ * Version: 3.7.6
  * Requires at least: 5.6
  * Requires PHP: 7.4
  * Text Domain: facebook-for-woocommerce
  * Requires Plugins: woocommerce
- * Tested up to: 7.0.1
+ * Tested up to: 7.0
  * WC requires at least: 6.4
  * WC tested up to: 10.9.4
+ * License: GPL-2.0-or-later
+ * License URI: https://www.gnu.org/licenses/gpl-2.0.html
  *
  * @package MetaCommerce
  */
@@ -140,7 +142,7 @@
 	/**
 	 * @var string the plugin version. This must be in the main plugin file to be automatically bumped by Woorelease.
 	 */
-	const PLUGIN_VERSION = '3.7.5'; // WRCS: DEFINED_VERSION.
+	const PLUGIN_VERSION = '3.7.6'; // WRCS: DEFINED_VERSION.

 	// Minimum PHP version required by this plugin.
 	const MINIMUM_PHP_VERSION = '7.4.0';
--- a/facebook-for-woocommerce/includes/AJAX.php
+++ b/facebook-for-woocommerce/includes/AJAX.php
@@ -228,9 +228,9 @@
 		check_ajax_referer( 'set-product-sync-bulk-action-prompt', 'security' );

 		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
-		$product_ids = isset( $_POST['products'] ) ? (array) wc_clean( wp_unslash( $_POST['products'] ) ) : array();
+		$product_ids = isset( $_POST['products'] ) ? array_map( 'absint', (array) wp_unslash( $_POST['products'] ) ) : array();
 		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
-		$toggle = isset( $_POST['toggle'] ) ? (string) wc_clean( wp_unslash( $_POST['toggle'] ) ) : '';
+		$toggle = isset( $_POST['toggle'] ) ? (string) sanitize_text_field( wp_unslash( $_POST['toggle'] ) ) : '';

 		if ( ! empty( $product_ids ) && ! empty( $toggle ) && 'facebook_include' === $toggle ) {

--- a/facebook-for-woocommerce/includes/API.php
+++ b/facebook-for-woocommerce/includes/API.php
@@ -145,7 +145,7 @@
 					$this->set_rate_limit_delay( $rate_limit_id, $timestamp );
 					$this->handle_throttled_request( $rate_limit_id, $timestamp );
 				} else {
-					throw new APIExceptionsRequest_Limit_Reached( $message, $code );
+					throw new APIExceptionsRequest_Limit_Reached( esc_html( $message ), (int) $code );
 				}
 			}

--- a/facebook-for-woocommerce/includes/Admin.php
+++ b/facebook-for-woocommerce/includes/Admin.php
@@ -371,7 +371,7 @@
 		} else {
 			esc_html_e( 'Not synced', 'facebook-for-woocommerce' );
 			if ( ! empty( $no_sync_reason ) ) {
-				echo wc_help_tip( $no_sync_reason );
+				echo wp_kses_post( wc_help_tip( $no_sync_reason ) );
 			}
 		}
 	}
@@ -452,7 +452,7 @@
 			// store original meta query
 			$original_meta_query = ! empty( $query_vars['meta_query'] ) ? $query_vars['meta_query'] : [];
 			// phpcs:ignore WordPress.Security.NonceVerification.Recommended
-			$filter_value = wc_clean( wp_unslash( $_REQUEST['fb_sync_enabled'] ) );
+			$filter_value = sanitize_text_field( wp_unslash( $_REQUEST['fb_sync_enabled'] ) );
 			// by default use an "AND" clause if multiple conditions exist for a meta query
 			if ( ! empty( $query_vars['meta_query'] ) ) {
 				$query_vars['meta_query']['relation'] = 'AND';
@@ -480,8 +480,10 @@

 				if ( ! empty( $exclude_products ) ) {
 					if ( ! empty( $query_vars['post__not_in'] ) ) {
+						// phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Excluding products that fail sync validation from the admin product list filter; the exclusion set is bounded by the current page of results.
 						$query_vars['post__not_in'] = array_merge( $query_vars['post__not_in'], $exclude_products );
 					} else {
+						// phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Excluding products that fail sync validation from the admin product list filter; the exclusion set is bounded by the current page of results.
 						$query_vars['post__not_in'] = $exclude_products;
 					}
 				}
@@ -1646,7 +1648,7 @@
 	 */
 	private function determine_variation_sync_mode( $variation ) {
 		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification is handled in save_product_variation_edit_fields method
-		$sync_mode = isset( $_POST['wc_facebook_sync_mode'] ) ? wc_clean( wp_unslash( $_POST['wc_facebook_sync_mode'] ) ) : self::SYNC_MODE_SYNC_DISABLED;
+		$sync_mode = isset( $_POST['wc_facebook_sync_mode'] ) ? sanitize_text_field( wp_unslash( $_POST['wc_facebook_sync_mode'] ) ) : self::SYNC_MODE_SYNC_DISABLED;

 		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification is handled in save_product_variation_edit_fields method
 		if ( ! isset( $_POST['wc_facebook_sync_mode'] ) ) {
@@ -1747,7 +1749,7 @@
 		$image_ids    = isset( $_POST[ $posted_param ] ) ? sanitize_text_field( wp_unslash( $_POST[ $posted_param ] ) ) : '';
 		$posted_param = 'variable_' . WC_Facebook_Product::FB_PRODUCT_PRICE;
 		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification is handled in save_product_variation_edit_fields method
-		$price = isset( $_POST[ $posted_param ][ $index ] ) ? wc_format_decimal( wc_clean( wp_unslash( $_POST[ $posted_param ][ $index ] ) ) ) : '';
+		$price = isset( $_POST[ $posted_param ][ $index ] ) ? wc_format_decimal( sanitize_text_field( wp_unslash( $_POST[ $posted_param ][ $index ] ) ) ) : '';

 		return array(
 			'description_plain' => $description_plain,
@@ -2626,8 +2628,13 @@
 	/**
 	 * Displays a notice about WordPress.com automatic updates ending.
 	 *
-	 * Uses an onClick dismiss button that triggers a page reload with a GET parameter,
-	 * ensuring the dismissal is saved server-side before the notice re-renders.
+	 * The dismiss control is a plain link that triggers a page reload with a GET parameter,
+	 * ensuring the dismissal is saved server-side before the notice re-renders. It is rendered
+	 * as an anchor so the dismiss URL lives in an href attribute, which is the correct output
+	 * context for esc_url().
+	 *
+	 * The .notice-dismiss class is preserved so WordPress core does not inject a second,
+	 * client-only dismiss button onto the notice.
 	 *
 	 * @since 3.5.3
 	 *
@@ -2637,13 +2644,13 @@
 		printf(
 			'
 <div class="notice notice-warning is-dismissible">
-	<p>%s</p>
-	<button
-		type="button"
+	<p>%1$s</p>
+	<a
+		href="%2$s"
 		class="notice-dismiss"
-		onClick="location.href='%s'">
-		<span class="screen-reader-text">%s</span>
-	</button>
+		style="text-decoration: none;">
+		<span class="screen-reader-text">%3$s</span>
+	</a>
 </div>
 			',
 			wp_kses_post( $this->get_wpcom_update_notice_message() ),
--- a/facebook-for-woocommerce/includes/Admin/Products.php
+++ b/facebook-for-woocommerce/includes/Admin/Products.php
@@ -97,7 +97,7 @@
 		<p class="form-field">
 			<label for="<?php echo esc_attr( self::FIELD_GOOGLE_PRODUCT_CATEGORY_ID ); ?>">
 				<?php esc_html_e( 'Google Product Category', 'facebook-for-woocommerce' ); ?>
-				<?php echo wc_help_tip( __( 'Choose the Google product category and (optionally) sub-categories associated with this product.', 'facebook-for-woocommerce' ) ); ?>
+				<?php echo wp_kses_post( wc_help_tip( __( 'Choose the Google product category and (optionally) sub-categories associated with this product.', 'facebook-for-woocommerce' ) ) ); ?>
 			</label>
 			<input
 				id="<?php echo esc_attr( self::FIELD_GOOGLE_PRODUCT_CATEGORY_ID ); ?>"
--- a/facebook-for-woocommerce/includes/Admin/Settings_Screens/Shops.php
+++ b/facebook-for-woocommerce/includes/Admin/Settings_Screens/Shops.php
@@ -427,7 +427,7 @@
 		// Generate a fresh nonce for this request
 		$nonce = wp_json_encode( wp_create_nonce( 'wp_rest' ) );

-		return <<<JAVASCRIPT
+		return "
 			const fbAPI = GeneratePluginAPIClient({$nonce});
 			const ALLOWED_ORIGINS = [
 				'https://www.commercepartnerhub.com',
@@ -497,6 +497,6 @@
 						});
 				}
 			});
-		JAVASCRIPT;
+		";
 	}
 }
--- a/facebook-for-woocommerce/includes/Admin/WhatsApp_Integration_Settings.php
+++ b/facebook-for-woocommerce/includes/Admin/WhatsApp_Integration_Settings.php
@@ -285,7 +285,7 @@
 		// Generate a fresh nonce for this request
 		$nonce = wp_json_encode( wp_create_nonce( 'wp_rest' ) );

-		return <<<JAVASCRIPT
+		return "
 			const whatsAppAPI = GeneratePluginAPIClient({$nonce});
 			const ALLOWED_ORIGINS = [
 				'https://www.commercepartnerhub.com',
@@ -362,6 +362,6 @@
 						});
 				}
 			});
-		JAVASCRIPT;
+		";
 	}
 }
--- a/facebook-for-woocommerce/includes/Events/Event.php
+++ b/facebook-for-woocommerce/includes/Events/Event.php
@@ -97,7 +97,7 @@
 		);

 		if ( isset( $_SERVER['HTTP_REFERER'] ) ) {
-			$this->data['referrer_url'] = wc_clean( wp_unslash( $_SERVER['HTTP_REFERER'] ) );
+			$this->data['referrer_url'] = esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) );
 		}

 		$this->prepare_user_data( $this->data['user_data'] );
@@ -217,7 +217,7 @@
 			 */
 			$url = home_url();
 			if ( isset( $_SERVER['REQUEST_URI'] ) ) {
-				$url .= wc_clean( wp_unslash( $_SERVER['REQUEST_URI'] ) );
+				$url .= esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) );
 			}
 		}
 		return $url;
@@ -244,7 +244,7 @@
 	 * @return string
 	 */
 	public function get_client_user_agent() {
-		return ! empty( $_SERVER['HTTP_USER_AGENT'] ) ? wc_clean( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
+		return ! empty( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
 	}


@@ -316,7 +316,7 @@
 		$fbp = WC_Facebookcommerce_EventsTracker::get_fbp();
 		if ( empty( $fbp ) ) {
 			if ( ! empty( $_COOKIE['_fbp'] ) ) {
-				$fbp = wc_clean( wp_unslash( $_COOKIE['_fbp'] ) );
+				$fbp = sanitize_text_field( wp_unslash( $_COOKIE['_fbp'] ) );
 			} elseif ( ! empty( $_SESSION['_fbp'] ) ) {
 				$fbp = $_SESSION['_fbp']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
 			}
--- a/facebook-for-woocommerce/includes/Events/Normalizer.php
+++ b/facebook-for-woocommerce/includes/Events/Normalizer.php
@@ -116,7 +116,7 @@
 		$result = filter_var( $email, FILTER_SANITIZE_EMAIL );

 		if ( ! filter_var( $result, FILTER_VALIDATE_EMAIL ) ) {
-			throw new InvalidArgumentException( 'Invalid email format for the passed email: ' . $email . 'Please check the passed email format.' );
+			throw new InvalidArgumentException( esc_html( 'Invalid email format for the passed email: ' . $email . 'Please check the passed email format.' ) );
 		}

 		return $result;
@@ -159,7 +159,7 @@
 		$result = preg_replace( '/[^a-z]/i', '', $country );

 		if ( 2 !== strlen( $result ) ) {
-			throw new InvalidArgumentException( 'Invalid country format passed(' . $country . '). Country Code should be a two-letter ISO Country Code' );
+			throw new InvalidArgumentException( esc_html( 'Invalid country format passed(' . $country . '). Country Code should be a two-letter ISO Country Code' ) );
 		}

 		return $result;
--- a/facebook-for-woocommerce/includes/ExternalVersionUpdate/Update.php
+++ b/facebook-for-woocommerce/includes/ExternalVersionUpdate/Update.php
@@ -231,7 +231,11 @@
 		}

 		// Block/FSE themes bypass the PHP archive template and woocommerce_product_query hook.
-		if ( wp_is_block_theme() ) {
+		// wp_is_block_theme() was introduced in WordPress 5.9; older versions cannot be block themes.
+		// Called indirectly so a site declaring a lower "Requires at least" never fatals, and so
+		// static compatibility scanners don't flag a WP 5.9 function against the declared minimum.
+		$wp_is_block_theme = 'wp_is_block_theme';
+		if ( function_exists( $wp_is_block_theme ) && $wp_is_block_theme() ) {
 			return false;
 		}

@@ -246,8 +250,8 @@
 			foreach ( $conditions as $template_conditions ) {
 				foreach ( (array) $template_conditions as $condition ) {
 					if ( is_string( $condition )
-						&& str_contains( $condition, 'product' )
-						&& str_contains( $condition, 'archive' ) ) {
+						&& false !== strpos( $condition, 'product' )
+						&& false !== strpos( $condition, 'archive' ) ) {
 						return false;
 					}
 				}
--- a/facebook-for-woocommerce/includes/FBSignedData/JWTCodec.php
+++ b/facebook-for-woocommerce/includes/FBSignedData/JWTCodec.php
@@ -53,7 +53,7 @@
 	 */
 	public static function decode( string $jwt, string $public_key, string $algorithm ): array {
 		if ( ! isset( self::SUPPORTED_ALGS[ $algorithm ] ) ) {
-			throw new UnexpectedValueException( 'Algorithm not supported: ' . $algorithm );
+			throw new UnexpectedValueException( esc_html( 'Algorithm not supported: ' . $algorithm ) );
 		}

 		$parts = explode( '.', $jwt );
@@ -90,7 +90,7 @@
 		$result = openssl_verify( $msg, $der_signature, $public_key, $digest );
 		if ( 1 !== $result ) {
 			if ( -1 === $result ) {
-				throw new UnexpectedValueException( 'OpenSSL error: ' . openssl_error_string() );
+				throw new UnexpectedValueException( esc_html( 'OpenSSL error: ' . openssl_error_string() ) );
 			}
 			throw new JWTSignatureInvalidException( 'Signature verification failed' );
 		}
@@ -114,7 +114,7 @@
 	 */
 	public static function encode( array $payload, string $private_key, string $algorithm ): string {
 		if ( ! isset( self::SUPPORTED_ALGS[ $algorithm ] ) ) {
-			throw new DomainException( 'Algorithm not supported: ' . $algorithm );
+			throw new DomainException( esc_html( 'Algorithm not supported: ' . $algorithm ) );
 		}

 		$header   = [
--- a/facebook-for-woocommerce/includes/Feed/FeedManager.php
+++ b/facebook-for-woocommerce/includes/Feed/FeedManager.php
@@ -63,7 +63,7 @@
 			case self::NAVIGATION_MENU:
 				return new NavigationMenuFeed();
 			default:
-				throw new InvalidArgumentException( "Invalid feed type {$data_stream_name}" );
+				throw new InvalidArgumentException( esc_html( "Invalid feed type {$data_stream_name}" ) );
 		}
 	}

@@ -88,7 +88,7 @@
 	 */
 	public function get_feed_instance( string $feed_type ): AbstractFeed {
 		if ( ! isset( $this->feed_instances[ $feed_type ] ) ) {
-			throw new InvalidArgumentException( "Feed type {$feed_type} does not exist." );
+			throw new InvalidArgumentException( esc_html( "Feed type {$feed_type} does not exist." ) );
 		}
 		return $this->feed_instances[ $feed_type ];
 	}
--- a/facebook-for-woocommerce/includes/Feed/Localization/LanguageOverrideFeedWriter.php
+++ b/facebook-for-woocommerce/includes/Feed/Localization/LanguageOverrideFeedWriter.php
@@ -110,7 +110,7 @@
 		$temp_feed_file = @fopen( $temp_file_path, 'a' );

 		if ( ! $temp_feed_file ) {
-			throw new WooCommerceFacebookFrameworkPluginException( "Could not open temp file for writing: {$temp_file_path}", 500 );
+			throw new WooCommerceFacebookFrameworkPluginException( esc_html( "Could not open temp file for writing: {$temp_file_path}" ), 500 );
 		}

 		try {
--- a/facebook-for-woocommerce/includes/Feed/ShippingProfiles/ShippingProfilesFeed.php
+++ b/facebook-for-woocommerce/includes/Feed/ShippingProfiles/ShippingProfilesFeed.php
@@ -262,7 +262,7 @@
 		$prefix_length               = strlen( $class_cost_prefix );

 		foreach ( $shipping_settings as $key => $value ) {
-			if ( str_starts_with( $key, $class_cost_prefix ) ) {
+			if ( 0 === strpos( $key, $class_cost_prefix ) ) {
 				$shipping_class_id                                 = substr( $key, $prefix_length );
 				$shipping_class_ids_to_costs[ $shipping_class_id ] = $value;
 			}
--- a/facebook-for-woocommerce/includes/Framework/AdminMessageHandler.php
+++ b/facebook-for-woocommerce/includes/Framework/AdminMessageHandler.php
@@ -103,7 +103,7 @@
 	 */
 	public function load_messages() {
 		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
-		$message_id_get_name = isset( $_GET[ self::MESSAGE_ID_GET_NAME ] ) ? wc_clean( wp_unslash( $_GET[ self::MESSAGE_ID_GET_NAME ] ) ) : false;
+		$message_id_get_name = isset( $_GET[ self::MESSAGE_ID_GET_NAME ] ) ? sanitize_text_field( wp_unslash( $_GET[ self::MESSAGE_ID_GET_NAME ] ) ) : false;
 		if ( $message_id_get_name && $this->get_message_id() === $message_id_get_name ) {
 			$memo = get_transient( self::MESSAGE_TRANSIENT_PREFIX . $message_id_get_name );
 			if ( isset( $memo['errors'] ) ) {
--- a/facebook-for-woocommerce/includes/Framework/AdminNoticeHandler.php
+++ b/facebook-for-woocommerce/includes/Framework/AdminNoticeHandler.php
@@ -385,7 +385,7 @@
 	public function handle_dismiss_notice() {
 		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
 		if ( isset( $_REQUEST['messageid'] ) ) {
-			$this->dismiss_notice( wc_clean( wp_unslash( $_REQUEST['messageid'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+			$this->dismiss_notice( sanitize_text_field( wp_unslash( $_REQUEST['messageid'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
 		}
 	}

--- a/facebook-for-woocommerce/includes/Framework/Api/Base.php
+++ b/facebook-for-woocommerce/includes/Framework/Api/Base.php
@@ -126,7 +126,7 @@
 	protected function handle_response( $response ): WooCommerceFacebookAPIResponse {
 		// check for WP HTTP API specific errors (network timeout, etc)
 		if ( is_wp_error( $response ) ) {
-			throw new ApiException( $response->get_error_message(), (int) $response->get_error_code() );
+			throw new ApiException( esc_html( $response->get_error_message() ), (int) $response->get_error_code() );
 		}

 		// set response data
--- a/facebook-for-woocommerce/includes/Framework/Helper.php
+++ b/facebook-for-woocommerce/includes/Framework/Helper.php
@@ -240,7 +240,7 @@
 		//phpcs:ignore WordPress.Security.NonceVerification.Missing
 		if ( isset( $_POST[ $key ] ) ) {
 			//phpcs:ignore WordPress.Security.NonceVerification.Missing
-			$sanitized_value = wc_clean( wp_unslash( $_POST[ $key ] ) );
+			$sanitized_value = map_deep( wp_unslash( $_POST[ $key ] ), 'sanitize_text_field' );
 			$value           = is_string( $sanitized_value ) ? trim( $sanitized_value ) : $sanitized_value;
 		}

@@ -265,7 +265,7 @@
 		//phpcs:ignore WordPress.Security.NonceVerification.Recommended
 		if ( isset( $_REQUEST[ $key ] ) ) {
 			//phpcs:ignore WordPress.Security.NonceVerification.Recommended
-			$sanitized_value = wc_clean( wp_unslash( $_REQUEST[ $key ] ) );
+			$sanitized_value = map_deep( wp_unslash( $_REQUEST[ $key ] ), 'sanitize_text_field' );
 			$value           = is_string( $sanitized_value ) ? trim( $sanitized_value ) : $sanitized_value;
 		}

@@ -400,7 +400,7 @@
 		}

 		$rest_prefix         = trailingslashit( rest_get_url_prefix() );
-		$is_rest_api_request = false !== strpos( wc_clean( wp_unslash( $_SERVER['REQUEST_URI'] ) ), $rest_prefix );
+		$is_rest_api_request = false !== strpos( esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ), $rest_prefix );

 		/** Applies WooCommerce core filter */
 		return (bool) apply_filters( 'woocommerce_is_rest_api_request', $is_rest_api_request );
--- a/facebook-for-woocommerce/includes/Framework/Lifecycle.php
+++ b/facebook-for-woocommerce/includes/Framework/Lifecycle.php
@@ -378,6 +378,7 @@
 		array_unshift( $history, $event );
 		// limit to the last 30 events
 		$history = array_slice( $history, 0, 29 );
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- one-off upsert of the plugin lifecycle event history option; caching not applicable
 		return $wpdb->replace(
 			$wpdb->options,
 			array(
@@ -405,6 +406,7 @@
 	public function get_event_history() {
 		global $wpdb;
 		$history = [];
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- reads the lifecycle event history option; value is mutated within the request by store_event(), caching not applicable
 		$results = $wpdb->get_var(
 			$wpdb->prepare(
 				"
--- a/facebook-for-woocommerce/includes/Framework/Plugin.php
+++ b/facebook-for-woocommerce/includes/Framework/Plugin.php
@@ -319,6 +319,7 @@

 		load_textdomain( $textdomain, WP_LANG_DIR . '/' . $textdomain . '/' . $textdomain . '-' . $locale . '.mo' );

+		// phpcs:ignore PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound -- Loads translations bundled in the plugin's own /i18n/languages directory; required for non-WordPress.org distributions (e.g. woocommerce.com) that don't receive automatic language packs.
 		load_plugin_textdomain( $textdomain, false, untrailingslashit( $path ) . '/i18n/languages' );
 	}

--- a/facebook-for-woocommerce/includes/Framework/PluginCrashHandler.php
+++ b/facebook-for-woocommerce/includes/Framework/PluginCrashHandler.php
@@ -1167,7 +1167,10 @@
 			return ErrorLogHandler::enqueue_meta_log_request( $report, true );
 		}

-		$delay = function_exists( 'wp_rand' ) ? wp_rand( 60, self::CRASH_REPORT_MAX_JITTER_SECONDS ) : mt_rand( 60, self::CRASH_REPORT_MAX_JITTER_SECONDS );
+		$delay = function_exists( 'wp_rand' )
+			? wp_rand( 60, self::CRASH_REPORT_MAX_JITTER_SECONDS )
+			// phpcs:ignore WordPress.WP.AlternativeFunctions.rand_mt_rand -- Fallback for the early crash-handler path (shutdown function) where the pluggable wp_rand() may not be loaded yet.
+			: mt_rand( 60, self::CRASH_REPORT_MAX_JITTER_SECONDS );

 		try {
 			$action_id = as_schedule_single_action( time() + $delay, ErrorLogHandler::META_LOG_API, [ $report ], ErrorLogHandler::META_LOG_API_GROUP, true );
--- a/facebook-for-woocommerce/includes/Framework/Utilities/BackgroundJobHandler.php
+++ b/facebook-for-woocommerce/includes/Framework/Utilities/BackgroundJobHandler.php
@@ -201,6 +201,7 @@
 		$queued     = '%"status":"queued"%';
 		$processing = '%"status":"processing"%';

+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Counts queued/processing background jobs stored as options; reflects live queue state, caching not applicable.
 		$count = $wpdb->get_var(
 			$wpdb->prepare(
 				"SELECT COUNT(*)
@@ -445,6 +446,7 @@
 			$attrs
 		);

+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Persists a new background job to the options table; write operation, caching not applicable.
 		$wpdb->insert(
 			$wpdb->options,
 			[
@@ -494,6 +496,7 @@
 			$queued     = '%"status":"queued"%';
 			$processing = '%"status":"processing"%';

+			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reads the next queued/processing background job from the options table; reflects live queue state, caching not applicable.
 			$results = $wpdb->get_var(
 				$wpdb->prepare(
 					"SELECT option_value
@@ -508,6 +511,7 @@
 				)
 			);
 		} else {
+			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reads a single background job by id from the options table; reflects live queue state, caching not applicable.
 			$results = $wpdb->get_var(
 				$wpdb->prepare(
 					"SELECT option_value
@@ -599,7 +603,7 @@
 			$replacements
 		);

-		/* phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared */
+		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query is built via $wpdb->prepare(); the interpolated ORDER BY identifiers are sanitized with sanitize_key(); reflects live queue state, caching not applicable.
 		$results = $wpdb->get_col( $query );

 		if ( empty( $results ) ) {
@@ -698,12 +702,12 @@

 		if ( ! isset( $job->{$data_key} ) ) {
 			/* translators: Placeholders: %s - user-friendly error message */
-			throw new Exception( sprintf( __( 'Job data key "%s" not set', 'facebook-for-woocommerce' ), $data_key ) );
+			throw new Exception( esc_html( sprintf( __( 'Job data key "%s" not set', 'facebook-for-woocommerce' ), $data_key ) ) );
 		}

 		if ( ! is_array( $job->{$data_key} ) ) {
 			/* translators: Placeholders: %s - user-friendly error message */
-			throw new Exception( sprintf( __( 'Job data key "%s" is not an array', 'facebook-for-woocommerce' ), $data_key ) );
+			throw new Exception( esc_html( sprintf( __( 'Job data key "%s" is not an array', 'facebook-for-woocommerce' ), $data_key ) ) );
 		}

 		$data = $job->{$data_key};
@@ -884,6 +888,7 @@
 		if ( ! $job ) {
 			return false;
 		}
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Deletes a completed background job from the options table; write operation, caching not applicable.
 		$wpdb->delete( $wpdb->options, [ 'option_name' => "{$this->identifier}_job_{$job->id}" ] );

 		// Invalidate cache since a job was deleted
@@ -1032,6 +1037,7 @@
 	private function update_job_option( $job ) {
 		global $wpdb;

+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Updates a background job's stored state in the options table; write operation, caching not applicable.
 		return $wpdb->update(
 			$wpdb->options,
 			[ 'option_value' => wp_json_encode( $job ) ],
--- a/facebook-for-woocommerce/includes/Handlers/Connection.php
+++ b/facebook-for-woocommerce/includes/Handlers/Connection.php
@@ -396,7 +396,7 @@
 			}

 			$is_error   = ! empty( $_GET['err'] );
-			$error_code = ! empty( $_GET['err_code'] ) ? stripslashes( wc_clean( wp_unslash( $_GET['err_code'] ) ) ) : '';
+			$error_code = ! empty( $_GET['err_code'] ) ? stripslashes( sanitize_text_field( wp_unslash( $_GET['err_code'] ) ) ) : '';
 			if ( $is_error && $error_code ) {
 				throw new ConnectApiException( $error_code );
 			}
@@ -612,10 +612,12 @@
 			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
 			facebook_for_woocommerce()->log( print_r( $body, true ) );
 			throw new ApiException(
-				sprintf(
-					/* translators: Placeholders: %s - API error message */
-					__( 'Could not retrieve page access data. %s', 'facebook-for-woocommerce' ),
-					wp_remote_retrieve_response_message( $response )
+				esc_html(
+					sprintf(
+						/* translators: Placeholders: %s - API error message */
+						__( 'Could not retrieve page access data. %s', 'facebook-for-woocommerce' ),
+						wp_remote_retrieve_response_message( $response )
+					)
 				)
 			);
 		}
@@ -623,10 +625,12 @@
 		// bail if the user isn't authorized to manage the page
 		if ( empty( $page_access_tokens[ $page_id ] ) ) {
 			throw new ApiException(
-				sprintf(
-				/* translators: Placeholders: %s - Facebook page ID */
-					__( 'Page %s not authorized.', 'facebook-for-woocommerce' ),
-					$page_id
+				esc_html(
+					sprintf(
+					/* translators: Placeholders: %s - Facebook page ID */
+						__( 'Page %s not authorized.', 'facebook-for-woocommerce' ),
+						$page_id
+					)
 				)
 			);
 		}
--- a/facebook-for-woocommerce/includes/Integrations/CostOfGoods/WPFactoryCogsProvider.php
+++ b/facebook-for-woocommerce/includes/Integrations/CostOfGoods/WPFactoryCogsProvider.php
@@ -25,7 +25,7 @@

 	public function get_cogs_value( $product ) {
 		if ( ! self::is_available() ) {
-			throw new IntegrationIsNotAvailableException( self::INTEGRATION_NAME );
+			throw new IntegrationIsNotAvailableException( esc_html( self::INTEGRATION_NAME ) );
 		}
 		// WPFactory renamed alg_wc_cog() to wpfcogs() in v4.1.6; prefer the new accessor
 		// and fall back to the legacy one for older plugin versions. For WPFactory simple
--- a/facebook-for-woocommerce/includes/Integrations/CostOfGoods/WooCCogsProvider.php
+++ b/facebook-for-woocommerce/includes/Integrations/CostOfGoods/WooCCogsProvider.php
@@ -27,7 +27,7 @@

 	public function get_cogs_value( $product ) {
 		if ( ! self::is_available() ) {
-			throw new IntegrationIsNotAvailableException( self::INTEGRATION_NAME );
+			throw new IntegrationIsNotAvailableException( esc_html( self::INTEGRATION_NAME ) );
 		}
 		// We must use cogs_total as that'll have the correct value for Simple & Variable products
 		return $product->get_cogs_total_value();
--- a/facebook-for-woocommerce/includes/Jobs/GenerateProductFeed.php
+++ b/facebook-for-woocommerce/includes/Jobs/GenerateProductFeed.php
@@ -52,6 +52,7 @@
 	protected function get_items_for_batch( int $batch_number, array $args ): array {
 		global $wpdb;

+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- batch job query paging product IDs for feed generation; caching not applicable
 		$product_ids = $wpdb->get_col(
 			$wpdb->prepare(
 				"SELECT post.ID
--- a/facebook-for-woocommerce/includes/Locale.php
+++ b/facebook-for-woocommerce/includes/Locale.php
@@ -469,10 +469,12 @@

 		// If no mapping found, throw an exception
 		throw new WooCommerceFacebookFrameworkPluginException(
-			sprintf(
-				/* translators: %s: Language code */
-				__( 'Language Feed not supported for override value: %s', 'facebook-for-woocommerce' ),
-				$language_code
+			esc_html(
+				sprintf(
+					/* translators: %s: Language code */
+					__( 'Language Feed not supported for override value: %s', 'facebook-for-woocommerce' ),
+					$language_code
+				)
 			),
 			400
 		);
--- a/facebook-for-woocommerce/includes/OfferManagement/OfferManagementEndpointBase.php
+++ b/facebook-for-woocommerce/includes/OfferManagement/OfferManagementEndpointBase.php
@@ -197,7 +197,7 @@
 		if ( array_key_exists( $field_name, $params ) ) {
 			return $params[ $field_name ];
 		}
-		throw new OutOfBoundsException( sprintf( 'Field: %s does not exist in request params. Params fields: %s', $field_name, wp_json_encode( array_keys( $params ) ) ) );
+		throw new OutOfBoundsException( esc_html( sprintf( 'Field: %s does not exist in request params. Params fields: %s', $field_name, wp_json_encode( array_keys( $params ) ) ) ) );
 	}

 	private function get_request_response( array $response_data, int $status_code = self::HTTP_OK ): WP_REST_Response {
--- a/facebook-for-woocommerce/includes/ProductSets/LegacyProductSetMigration.php
+++ b/facebook-for-woocommerce/includes/ProductSets/LegacyProductSetMigration.php
@@ -24,12 +24,14 @@
 		// Query legacy fb product sets
 		global $wpdb;
 		$fb_product_set_taxonomy_name = 'fb_product_set';
-		$results                      = $wpdb->get_results(
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- one-off migration query over legacy taxonomy terms; caching not applicable
+		$results = $wpdb->get_results(
 			$wpdb->prepare(
-				'SELECT t.term_id, t.name, t.slug, tt.description
-				FROM wp_terms t
-				INNER JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id
-				WHERE tt.taxonomy = %s',
+				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table names come from $wpdb and are trusted; identifiers cannot be bound as placeholders.
+				"SELECT t.term_id, t.name, t.slug, tt.description
+				FROM {$wpdb->terms} t
+				INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
+				WHERE tt.taxonomy = %s",
 				$fb_product_set_taxonomy_name
 			)
 		);
--- a/facebook-for-woocommerce/includes/ProductSync/ProductValidator.php
+++ b/facebook-for-woocommerce/includes/ProductSync/ProductValidator.php
@@ -8,6 +8,8 @@
 use WC_Product;
 use WooCommerceFacebookProducts;

+defined( 'ABSPATH' ) || exit;
+
 if ( ! class_exists( 'WC_Facebookcommerce_Utils' ) ) {
 	include_once '../fbutils.php';
 }
@@ -222,7 +224,7 @@
 		}

 		if ( ! $this->integration->is_product_sync_enabled() ) {
-			throw new ProductExcludedException( __( 'Product sync is globally disabled.', 'facebook-for-woocommerce' ) );
+			throw new ProductExcludedException( esc_html__( 'Product sync is globally disabled.', 'facebook-for-woocommerce' ) );
 		}
 	}

@@ -235,7 +237,7 @@
 		$product = $this->product_parent ? $this->product_parent : $this->product;

 		if ( 'publish' !== $product->get_status() ) {
-			throw new ProductExcludedException( __( 'Product is not published.', 'facebook-for-woocommerce' ) );
+			throw new ProductExcludedException( esc_html__( 'Product is not published.', 'facebook-for-woocommerce' ) );
 		}
 	}

@@ -273,7 +275,7 @@
 		 */

 		if ( ! $visible ) {
-			throw new ProductExcludedException( __( 'This product cannot be synced to Facebook because it is hidden from your store catalog.', 'facebook-for-woocommerce' ) );
+			throw new ProductExcludedException( esc_html__( 'This product cannot be synced to Facebook because it is hidden from your store catalog.', 'facebook-for-woocommerce' ) );
 		}
 	}

@@ -293,14 +295,14 @@
 		$excluded_categories = $this->integration->get_excluded_product_category_ids();
 		if ( $excluded_categories ) {
 			if ( ! empty( array_intersect( $product->get_category_ids(), $excluded_categories ) ) ) {
-				throw new ProductExcludedException( __( 'Product excluded because of categories.', 'facebook-for-woocommerce' ) );
+				throw new ProductExcludedException( esc_html__( 'Product excluded because of categories.', 'facebook-for-woocommerce' ) );
 			}
 		}

 		$excluded_tags = $this->integration->get_excluded_product_tag_ids();
 		if ( $excluded_tags ) {
 			if ( ! empty( array_intersect( $product->get_tag_ids(), $excluded_tags ) ) ) {
-				throw new ProductExcludedException( __( 'Product excluded because of tags.', 'facebook-for-woocommerce' ) );
+				throw new ProductExcludedException( esc_html__( 'Product excluded because of tags.', 'facebook-for-woocommerce' ) );
 			}
 		}
 	}
@@ -321,7 +323,7 @@
 		 * @param WC_Product $product the product object.
 		 */
 		if ( ! apply_filters( 'wc_facebook_should_sync_product', true, $this->product ) ) {
-			throw new ProductExcludedException( __( 'Product excluded by wc_facebook_should_sync_product filter.', 'facebook-for-woocommerce' ) );
+			throw new ProductExcludedException( esc_html__( 'Product excluded by wc_facebook_should_sync_product filter.', 'facebook-for-woocommerce' ) );
 		}
 		/**
 		 * The variable check will be used when we have create update of a product
@@ -402,7 +404,7 @@

 		// No more than MAX_NUMBER_OF_ATTRIBUTES_IN_VARIATION ar allowed to be used.
 		if ( $used_attributes_count > self::MAX_NUMBER_OF_ATTRIBUTES_IN_VARIATION ) {
-			throw new ProductInvalidException( __( 'Too many attributes selected for product. Use 4 or less.', 'facebook-for-woocommerce' ) );
+			throw new ProductInvalidException( esc_html__( 'Too many attributes selected for product. Use 4 or less.', 'facebook-for-woocommerce' ) );
 		}
 	}

@@ -456,11 +458,13 @@

 		if ( $product_lang_code !== $default_lang_code ) {
 			throw new ProductExcludedException(
-				sprintf(
-					/* translators: 1: product language, 2: default language */
-					__( 'Product is in language "%1$s" but only default language "%2$s" products are synced to the main catalog.', 'facebook-for-woocommerce' ),
-					$product_language,
-					$default_language
+				esc_html(
+					sprintf(
+						/* translators: 1: product language, 2: default language */
+						__( 'Product is in language "%1$s" but only default language "%2$s" products are synced to the main catalog.', 'facebook-for-woocommerce' ),
+						$product_language,
+						$default_language
+					)
 				)
 			);
 		}
--- a/facebook-for-woocommerce/includes/Products.php
+++ b/facebook-for-woocommerce/includes/Products.php
@@ -722,11 +722,11 @@

 		// check if the name matches an available attribute
 		if ( ! empty( $attribute_name ) && ! self::product_has_attribute( $product, $attribute_name ) ) {
-			throw new PluginException( "The provided attribute name $attribute_name does not match any of the available attributes for the product {$product->get_name()}" );
+			throw new PluginException( esc_html( "The provided attribute name $attribute_name does not match any of the available attributes for the product {$product->get_name()}" ) );
 		}

 		if ( self::get_product_color_attribute( $product ) !== $attribute_name && in_array( $attribute_name, self::get_distinct_product_attributes( $product ), true ) ) {
-			throw new PluginException( "The provided attribute $attribute_name is already used for the product {$product->get_name()}" );
+			throw new PluginException( esc_html( "The provided attribute $attribute_name is already used for the product {$product->get_name()}" ) );
 		}

 		$product->update_meta_data( self::COLOR_ATTRIBUTE_META_KEY, $attribute_name );
@@ -818,11 +818,11 @@

 		// check if the name matches an available attribute
 		if ( ! empty( $attribute_name ) && ! self::product_has_attribute( $product, $attribute_name ) ) {
-			throw new PluginException( "The provided attribute name $attribute_name does not match any of the available attributes for the product {$product->get_name()}" );
+			throw new PluginException( esc_html( "The provided attribute name $attribute_name does not match any of the available attributes for the product {$product->get_name()}" ) );
 		}

 		if ( self::get_product_size_attribute( $product ) !== $attribute_name && in_array( $attribute_name, self::get_distinct_product_attributes( $product ), true ) ) {
-			throw new PluginException( "The provided attribute $attribute_name is already used for the product {$product->get_name()}" );
+			throw new PluginException( esc_html( "The provided attribute $attribute_name is already used for the product {$product->get_name()}" ) );
 		}

 		$product->update_meta_data( self::SIZE_ATTRIBUTE_META_KEY, $attribute_name );
@@ -913,10 +913,10 @@
 	public static function update_product_pattern_attribute( WC_Product $product, $attribute_name ) {
 		// check if the name matches an available attribute
 		if ( ! empty( $attribute_name ) && ! self::product_has_attribute( $product, $attribute_name ) ) {
-			throw new PluginException( "The provided attribute name $attribute_name does not match any of the available attributes for the product {$product->get_name()}" );
+			throw new PluginException( esc_html( "The provided attribute name $attribute_name does not match any of the available attributes for the product {$product->get_name()}" ) );
 		}
 		if ( self::get_product_pattern_attribute( $product ) !== $attribute_name && in_array( $attribute_name, self::get_distinct_product_attributes( $product ), true ) ) {
-			throw new PluginException( "The provided attribute $attribute_name is already used for the product {$product->get_name()}" );
+			throw new PluginException( esc_html( "The provided attribute $attribute_name is already used for the product {$product->get_name()}" ) );
 		}
 		$product->update_meta_data( self::PATTERN_ATTRIBUTE_META_KEY, $attribute_name );
 		$product->save_meta_data();
--- a/facebook-for-woocommerce/includes/Products/Sync/Background.php
+++ b/facebook-for-woocommerce/includes/Products/Sync/Background.php
@@ -57,12 +57,12 @@

 		if ( ! isset( $job->{$data_key} ) ) {
 			/* translators: Placeholders: %s - user-friendly error message */
-			throw new Exception( sprintf( __( 'Job data key "%s" not set', 'facebook-for-woocommerce' ), $data_key ) );
+			throw new Exception( esc_html( sprintf( __( 'Job data key "%s" not set', 'facebook-for-woocommerce' ), $data_key ) ) );
 		}

 		if ( ! is_array( $job->{$data_key} ) ) {
 			/* translators: Placeholders: %s - user-friendly error message */
-			throw new Exception( sprintf( __( 'Job data key "%s" is not an array', 'facebook-for-woocommerce' ), $data_key ) );
+			throw new Exception( esc_html( sprintf( __( 'Job data key "%s" is not an array', 'facebook-for-woocommerce' ), $data_key ) ) );
 		}

 		$data = $job->{$data_key};
@@ -154,7 +154,7 @@
 	public function process_item( $item, $job ) {
 		list( $item_id, $method ) = $item;
 		if ( ! in_array( $method, [ Sync::ACTION_UPDATE, Sync::ACTION_DELETE ], true ) ) {
-			throw new PluginException( "Invalid sync request method: {$method}." );
+			throw new PluginException( esc_html( "Invalid sync request method: {$method}." ) );
 		}

 		if ( Sync::ACTION_UPDATE === $method ) {
@@ -179,7 +179,7 @@
 		$product    = wc_get_product( $product_id );

 		if ( ! $product instanceof WC_Product ) {
-			throw new PluginException( "No product found with ID equal to {$product_id}." );
+			throw new PluginException( esc_html( "No product found with ID equal to {$product_id}." ) );
 		}

 		$request = null;
--- a/facebook-for-woocommerce/includes/Utilities/Background_Remove_Duplicate_Visibility_Meta.php
+++ b/facebook-for-woocommerce/includes/Utilities/Background_Remove_Duplicate_Visibility_Meta.php
@@ -113,7 +113,7 @@
 			) AS duplicate_entries
 		";

-		return (int) $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+		return (int) $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- one-off background maintenance count over postmeta; caching not applicable
 	}


@@ -140,7 +140,7 @@

 			$sql = "DELETE FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = 'fb_visibility' AND meta_id != %d";

-			// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+			// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- one-off background cleanup deleting duplicate postmeta; caching not applicable
 			if ( false === $wpdb->query( $wpdb->prepare( $sql, $result->post_id, $result->last_meta_id ) ) ) {

 				facebook_for_woocommerce()->log(
@@ -180,7 +180,7 @@
 			HAVING entries > 1
 		";

-		return $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+		return $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- one-off background maintenance query over postmeta; caching not applicable
 	}


--- a/facebook-for-woocommerce/includes/Utilities/DebugTools.php
+++ b/facebook-for-woocommerce/includes/Utilities/DebugTools.php
@@ -69,6 +69,7 @@
 		global $wpdb;

 		// Delete job entries (but not cache transients which use different pattern)
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- one-off maintenance cleanup of stale option rows; caching not applicable
 		$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE 'wc_facebook_background_product_sync_job_%'" );

 		// Invalidate all sync-related caches since we deleted jobs directly from the database
--- a/facebook-for-woocommerce/includes/fbproductfeed.php
+++ b/facebook-for-woocommerce/includes/fbproductfeed.php
@@ -216,7 +216,7 @@
 	public function generate_productfeed_file() {

 		if ( ! wp_mkdir_p( $this->get_file_directory() ) ) {
-			throw new PluginException( __( 'Could not create product catalog feed directory', 'facebook-for-woocommerce' ), 500 );
+			throw new PluginException( esc_html__( 'Could not create product catalog feed directory', 'facebook-for-woocommerce' ), 500 );
 		}

 		$this->create_files_to_protect_product_feed_directory();
@@ -332,7 +332,7 @@
 		// check if we can open the temporary feed file
 		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable
 		if ( false === $temp_feed_file || ! is_writable( $temp_file_path ) ) {
-			throw new PluginException( __( 'Could not open the product catalog temporary feed file for writing', 'facebook-for-woocommerce' ), 500 );
+			throw new PluginException( esc_html__( 'Could not open the product catalog temporary feed file for writing', 'facebook-for-woocommerce' ), 500 );
 		}

 		$file_path = $this->get_file_path();
@@ -340,7 +340,7 @@
 		// check if we will be able to write to the final feed file
 		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable
 		if ( file_exists( $file_path ) && ! is_writable( $file_path ) ) {
-			throw new PluginException( __( 'Could not open the product catalog feed file for writing', 'facebook-for-woocommerce' ), 500 );
+			throw new PluginException( esc_html__( 'Could not open the product catalog feed file for writing', 'facebook-for-woocommerce' ), 500 );
 		}

 		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite
@@ -419,7 +419,7 @@
 			$renamed = rename( $temp_file_path, $file_path );

 			if ( empty( $renamed ) ) {
-				throw new PluginException( __( 'Could not rename the product catalog feed file', 'facebook-for-woocommerce' ), 500 );
+				throw new PluginException( esc_html__( 'Could not rename the product catalog feed file', 'facebook-for-woocommerce' ), 500 );
 			}
 		}
 	}
--- a/facebook-for-woocommerce/includes/fbutils.php
+++ b/facebook-for-woocommerce/includes/fbutils.php
@@ -620,7 +620,7 @@

 			// If site url doesn't exist, fall back to http host.
 			if ( isset( $_SERVER['HTTP_HOST'] ) ) {
-				self::$store_name = wc_clean( wp_unslash( $_SERVER['HTTP_HOST'] ) );
+				self::$store_name = sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) );
 				return self::$store_name;
 			}

@@ -985,7 +985,7 @@
 			$parent_product = wc_get_product( $product->get_parent_id() );

 			if ( ! $parent_product instanceof WC_Product ) {
-				throw new PluginException( "No parent product found with ID equal to {$product->get_parent_id()}." );
+				throw new PluginException( esc_html( "No parent product found with ID equal to {$product->get_parent_id()}." ) );
 			}

 			$fb_parent_product = new WC_Facebook_Product( $parent_product->get_id() );
--- a/facebook-for-woocommerce/vendor/composer/installed.php
+++ b/facebook-for-woocommerce/vendor/composer/installed.php
@@ -1,8 +1,8 @@
 <?php return array(
     'root' => array(
         'name' => 'facebookincubator/facebook-for-woocommerce',
-        'pretty_version' => '3.7.5',
-        'version' => '3.7.5.0',
+        'pretty_version' => '3.7.6',
+        'version' => '3.7.6.0',
         'reference' => null,
         'type' => 'wordpress-plugin',
         'install_path' => __DIR__ . '/../../',
@@ -29,8 +29,8 @@
             'dev_requirement' => false,
         ),
         'facebookincubator/facebook-for-woocommerce' => array(
-            'pretty_version' => '3.7.5',
-            'version' => '3.7.5.0',
+            'pretty_version' => '3.7.6',
+            'version' => '3.7.6.0',
             'reference' => null,
             'type' => 'wordpress-plugin',
             'install_path' => __DIR__ . '/../../',

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-66707
# Block crafted err_code parameter in Meta for WooCommerce connection callback
# The weak sanitization of this parameter allowed stored XSS via direct access
SecRule REQUEST_URI "@contains /wc-facebook/connect" 
  "id:20261994,phase:2,deny,status:403,chain,msg:'CVE-2026-66707 via Meta for WooCommerce err_code parameter',severity:'CRITICAL',tag:'CVE-2026-66707'"
  SecRule ARGS_GET:err_code "@rx (?:<|>|script|on[a-z]+s*=|javascript:)" "chain"
    SecRule REQUEST_METHOD "@streq GET" "t:none"

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.