Commit 6fdf0963b85 for woocommerce
commit 6fdf0963b8559985ecf4a465e07a5de7f373c3c2
Author: Justin P <228780+layoutd@users.noreply.github.com>
Date: Fri Sep 4 10:43:42 2026 +0200
Harden wcAnalytics config global and webpack public path assignment (#68346)
diff --git a/packages/php/woocommerce-analytics/changelog/wooa7s-2042-config-global-hardening b/packages/php/woocommerce-analytics/changelog/wooa7s-2042-config-global-hardening
new file mode 100644
index 00000000000..84aa4c4bcf3
--- /dev/null
+++ b/packages/php/woocommerce-analytics/changelog/wooa7s-2042-config-global-hardening
@@ -0,0 +1,4 @@
+Significance: patch
+Type: security
+
+Always initialize the front-end `window.wcAnalytics` object from scratch and only accept a string as the webpack public path.
diff --git a/packages/php/woocommerce-analytics/src/class-universal.php b/packages/php/woocommerce-analytics/src/class-universal.php
index bed286783c6..7a466211d6a 100644
--- a/packages/php/woocommerce-analytics/src/class-universal.php
+++ b/packages/php/woocommerce-analytics/src/class-universal.php
@@ -82,7 +82,8 @@ class Universal {
?>
<script type="text/javascript">
(function() {
- window.wcAnalytics = window.wcAnalytics || {};
+ // Always start from a fresh object: a `||` fallback would keep a same-named DOM element alive.
+ window.wcAnalytics = {};
const wcAnalytics = window.wcAnalytics;
// Set the assets URL for webpack to find the split assets.
diff --git a/packages/php/woocommerce-analytics/src/client/public-path.ts b/packages/php/woocommerce-analytics/src/client/public-path.ts
index 8af63d875d9..91cae49e2c8 100644
--- a/packages/php/woocommerce-analytics/src/client/public-path.ts
+++ b/packages/php/woocommerce-analytics/src/client/public-path.ts
@@ -6,8 +6,11 @@
* Unfortunately we can't set `publicPath: 'auto'` because WordPress.com Simple's JS concatenation breaks it (and other plugins that do JS concatenation probably would too).
* @see https://webpack.js.org/guides/public-path/#on-the-fly
*/
-if ( typeof window === 'object' && window.wcAnalytics?.assets_url ) {
+const assetsUrl = typeof window === 'object' ? window.wcAnalytics?.assets_url : undefined;
+
+// Only a string may become the public path: a same-named DOM element is truthy and stringifies to its href.
+if ( typeof assetsUrl === 'string' ) {
// @ts-expect-error: __webpack_public_path__ is set globally by webpack, ignore TS2304
// eslint-disable-next-line no-global-assign
- __webpack_public_path__ = window.wcAnalytics.assets_url;
+ __webpack_public_path__ = assetsUrl;
}
diff --git a/packages/php/woocommerce-analytics/tests/php/Universal_Page_Output_Test.php b/packages/php/woocommerce-analytics/tests/php/Universal_Page_Output_Test.php
index 5229e5c7d9a..298f2b71dc1 100644
--- a/packages/php/woocommerce-analytics/tests/php/Universal_Page_Output_Test.php
+++ b/packages/php/woocommerce-analytics/tests/php/Universal_Page_Output_Test.php
@@ -312,6 +312,18 @@ class Universal_Page_Output_Test extends BaseTestCase {
);
}
+ /**
+ * Test that the page output always starts `window.wcAnalytics` from a fresh
+ * object. A `window.wcAnalytics || {}` fallback keeps a same-named DOM element
+ * alive, and the assignments that follow silently fail to land on it.
+ */
+ public function test_page_output_initializes_config_from_a_fresh_object(): void {
+ $output = $this->render_analytics_data();
+
+ $this->assertStringContainsString( 'window.wcAnalytics = {};', $output );
+ $this->assertStringNotContainsString( 'window.wcAnalytics ||', $output );
+ }
+
/**
* Test that the server-fired pixel path keeps its request-derived
* properties. That path runs on uncached requests and is the only place