Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/google-analytics-for-wordpress/googleanalytics.php
+++ b/google-analytics-for-wordpress/googleanalytics.php
@@ -7,7 +7,7 @@
* Author: MonsterInsights
* Author URI: https://www.monsterinsights.com/lite/?utm_source=liteplugin&utm_medium=pluginheader&utm_campaign=authoruri&utm_content=7%2E0%2E0
*
- * Version: 10.1.2
+ * Version: 10.1.3
* Requires at least: 5.6.0
* Requires PHP: 7.2
*
@@ -79,7 +79,7 @@
* @access public
* @var string $version Plugin version.
*/
- public $version = '10.1.2';
+ public $version = '10.1.3';
/**
* Plugin file.
*
--- a/google-analytics-for-wordpress/includes/admin/admin-assets.php
+++ b/google-analytics-for-wordpress/includes/admin/admin-assets.php
@@ -285,7 +285,7 @@
'auth' => $auth_data,
'authed' => $site_auth || $ms_auth, // Boolean for admin bar compatibility
'plugin_version' => MONSTERINSIGHTS_VERSION,
- 'wizard_url' => monsterinsights_get_onboarding_url(),
+ 'wizard_url' => monsterinsights_can_install_plugins() ? monsterinsights_get_onboarding_url() : '',
'rest_url' => get_rest_url(),
'rest_nonce' => wp_create_nonce( 'wp_rest' ),
// Direct API access (bypasses WordPress for performance).
@@ -929,7 +929,7 @@
'bearer_expires' => $bearer_expires,
// Sample data mode: when true, frontend should bypass direct API and use WP AJAX for sample data.
'sample_data_enabled' => apply_filters( 'monsterinsights_sample_data_enabled', false ),
- 'wizard_url' => is_admin() ? monsterinsights_get_onboarding_url() : '',
+ 'wizard_url' => monsterinsights_can_install_plugins() ? monsterinsights_get_onboarding_url() : '',
'addons' => $addons_active,
'addons_info' => $addons_info,
'activate_nonce' => wp_create_nonce( 'monsterinsights-activate' ),
--- a/google-analytics-for-wordpress/includes/admin/class-monsterinsights-onboarding.php
+++ b/google-analytics-for-wordpress/includes/admin/class-monsterinsights-onboarding.php
@@ -113,7 +113,20 @@
if ( empty( $provided_key ) || false === $stored_key || ! hash_equals( $stored_key, $provided_key ) ) {
return new WP_Error(
'monsterinsights_invalid_key',
- 'Invalid onboarding key',
+ esc_html__( 'Invalid onboarding key', 'google-analytics-for-wordpress' ),
+ array( 'status' => 403 )
+ );
+ }
+
+ // Ensure the user who generated the key has plugin installation capability.
+ // Only enforce when the user ID transient is present; if it has been evicted
+ // from the object cache independently of the key transient, skip this check
+ // rather than blocking a legitimate upgrade flow.
+ $onboarding_user_id = monsterinsights_get_onboarding_user_id();
+ if ( $onboarding_user_id && ! monsterinsights_can_install_plugins( $onboarding_user_id ) ) {
+ return new WP_Error(
+ 'monsterinsights_insufficient_permissions',
+ esc_html__( 'Insufficient permissions', 'google-analytics-for-wordpress' ),
array( 'status' => 403 )
);
}
--- a/google-analytics-for-wordpress/includes/connect.php
+++ b/google-analytics-for-wordpress/includes/connect.php
@@ -24,7 +24,7 @@
public function hooks() {
add_action( 'wp_ajax_monsterinsights_connect_url', array( $this, 'generate_connect_url' ) );
- add_action( 'wp_ajax_nopriv_monsterinsights_connect_process', array( $this, 'process' ) );
+ add_action( 'wp_ajax_monsterinsights_connect_process', array( $this, 'process' ) );
}
/**
@@ -141,6 +141,11 @@
'</a>'
);
+ // Check for permissions.
+ if ( ! monsterinsights_can_install_plugins() ) {
+ wp_send_json_error( $error );
+ }
+
// verify params present (oth & download link).
$post_oth = ! empty( $_REQUEST['oth'] ) ? sanitize_text_field($_REQUEST['oth']) : '';
$post_url = ! empty( $_REQUEST['file'] ) ? sanitize_url($_REQUEST['file']) : '';
--- a/google-analytics-for-wordpress/includes/frontend/frontend.php
+++ b/google-analytics-for-wordpress/includes/frontend/frontend.php
@@ -161,6 +161,23 @@
}
/**
+ * Whether the built React admin bar bundle exists on disk.
+ *
+ * The top-level "Insights" admin bar button is interactive only when the
+ * React app is enqueued and mounts. If the bundle is missing (e.g. a
+ * release packaging gap), the rendered link has no click handler and
+ * appears broken to users. This helper lets callers gate on availability.
+ *
+ * @return bool
+ */
+function monsterinsights_admin_bar_assets_available() {
+ $version = monsterinsights_is_pro_version() ? 'pro' : 'lite';
+ $asset_file = MONSTERINSIGHTS_PLUGIN_DIR . "{$version}/assets/admin-bar/index.asset.php";
+
+ return file_exists( $asset_file );
+}
+
+/**
* Add an admin bar menu item on the frontend.
*
* @return void
@@ -171,13 +188,26 @@
return;
}
+ // If the React admin bar bundle is missing, skip adding the button —
+ // otherwise it renders as an inert link and looks broken. The
+ // "Insights" entry under wp-logo (see em-admin.php) still works.
+ if ( ! monsterinsights_admin_bar_assets_available() ) {
+ return;
+ }
+
global $wp_admin_bar;
+ // Fallback href so the button degrades to navigation if the React
+ // app fails to mount for any reason (JS error, CSP, extension).
+ $reports_url = is_network_admin()
+ ? add_query_arg( 'page', 'monsterinsights_overview_report', network_admin_url( 'admin.php' ) )
+ : add_query_arg( 'page', 'monsterinsights_reports', admin_url( 'admin.php' ) );
+
$args = array(
'id' => 'monsterinsights_frontend_button',
'title' => '<span class="ab-icon dashicons-before dashicons-chart-bar"></span> Insights',
// Maybe allow translation?
- 'href' => '#',
+ 'href' => $reports_url,
);
if ( method_exists( $wp_admin_bar, 'add_menu' ) ) {
@@ -214,7 +244,7 @@
$version = monsterinsights_is_pro_version() ? 'pro' : 'lite';
$asset_file = MONSTERINSIGHTS_PLUGIN_DIR . "{$version}/assets/admin-bar/index.asset.php";
- if (!file_exists($asset_file)) {
+ if ( ! monsterinsights_admin_bar_assets_available() ) {
return;
}
@@ -249,40 +279,52 @@
plugin_dir_path( MONSTERINSIGHTS_PLUGIN_FILE ) . $version . '/languages'
);
- // Localize data (same structure as Vue version for compatibility)
- $page_title = is_singular() ? get_the_title() : monsterinsights_get_page_title();
- $site_auth = MonsterInsights()->auth->get_viewname();
- $ms_auth = is_multisite() && MonsterInsights()->auth->get_network_viewname();
-
- // Check if any of the other admin scripts are enqueued, if so, use their object.
- if ( ! wp_script_is( 'monsterinsights-vue-script' ) && ! wp_script_is( 'monsterinsights-vue-reports' ) && ! wp_script_is( 'monsterinsights-vue-widget' ) && ! wp_script_is( 'monsterinsights-vue3-custom-dashboard' ) ) {
- $reports_url = is_network_admin() ? add_query_arg( 'page', 'monsterinsights_overview_report', network_admin_url( 'admin.php' ) ) : add_query_arg( 'page', 'monsterinsights_reports', admin_url( 'admin.php' ) );
- wp_localize_script(
- 'monsterinsights-admin-bar',
- 'monsterinsights',
- array(
- 'ajax' => admin_url( 'admin-ajax.php' ),
- 'nonce' => wp_create_nonce( 'mi-admin-nonce' ),
- 'network' => is_network_admin(),
- 'assets' => plugins_url( $version . '/assets/admin-bar', MONSTERINSIGHTS_PLUGIN_FILE ),
- 'addons_url' => is_multisite() ? network_admin_url( 'admin.php?page=monsterinsights_network#/addons' ) : admin_url( 'admin.php?page=monsterinsights_settings#/addons' ),
- 'page_id' => is_singular() ? get_the_ID() : false,
- 'page_title' => $page_title,
- 'plugin_version' => MONSTERINSIGHTS_VERSION,
- 'shareasale_id' => monsterinsights_get_shareasale_id(),
- 'shareasale_url' => monsterinsights_get_shareasale_url( monsterinsights_get_shareasale_id(), '' ),
- 'is_admin' => is_admin(),
- 'reports_url' => $reports_url,
- 'authed' => $site_auth || $ms_auth,
- 'auth_connect_url' => is_network_admin() ? network_admin_url( 'index.php?page=monsterinsights-onboarding' ) : admin_url( 'index.php?page=monsterinsights-onboarding' ),
- 'getting_started_url' => is_multisite() ? network_admin_url( 'admin.php?page=monsterinsights_network#/about/getting-started' ) : admin_url( 'admin.php?page=monsterinsights_settings#/about/getting-started' ),
- 'wizard_url' => is_network_admin() ? network_admin_url( 'index.php?page=monsterinsights-onboarding' ) : admin_url( 'index.php?page=monsterinsights-onboarding' ),
- 'roles_manage_options' => monsterinsights_get_manage_options_roles(),
- 'user_roles' => $current_user->roles,
- 'roles_view_reports' => monsterinsights_get_option('view_reports'),
- )
- );
+ // Skip localizing the shared `monsterinsights` global if another MI app already owns it —
+ // otherwise this admin-bar payload would clobber config like relay_api_url / license / reporting_api.
+ $competing_handles = array(
+ 'monsterinsights-vue-script',
+ 'monsterinsights-vue-reports',
+ 'monsterinsights-vue-widget',
+ 'monsterinsights-vue3-custom-dashboard',
+ 'monsterinsights-vue3-reports',
+ );
+
+ foreach ( $competing_handles as $handle ) {
+ if ( wp_script_is( $handle ) ) {
+ return;
+ }
}
+
+ // Localize data (same structure as Vue version for compatibility)
+ $page_title = is_singular() ? get_the_title() : monsterinsights_get_page_title();
+ $site_auth = MonsterInsights()->auth->get_viewname();
+ $ms_auth = is_multisite() && MonsterInsights()->auth->get_network_viewname();
+ $reports_url = is_network_admin() ? add_query_arg( 'page', 'monsterinsights_overview_report', network_admin_url( 'admin.php' ) ) : add_query_arg( 'page', 'monsterinsights_reports', admin_url( 'admin.php' ) );
+ wp_localize_script(
+ 'monsterinsights-admin-bar',
+ 'monsterinsights',
+ array(
+ 'ajax' => admin_url( 'admin-ajax.php' ),
+ 'nonce' => wp_create_nonce( 'mi-admin-nonce' ),
+ 'network' => is_network_admin(),
+ 'assets' => plugins_url( $version . '/assets/admin-bar', MONSTERINSIGHTS_PLUGIN_FILE ),
+ 'addons_url' => is_multisite() ? network_admin_url( 'admin.php?page=monsterinsights_network#/addons' ) : admin_url( 'admin.php?page=monsterinsights_settings#/addons' ),
+ 'page_id' => is_singular() ? get_the_ID() : false,
+ 'page_title' => $page_title,
+ 'plugin_version' => MONSTERINSIGHTS_VERSION,
+ 'shareasale_id' => monsterinsights_get_shareasale_id(),
+ 'shareasale_url' => monsterinsights_get_shareasale_url( monsterinsights_get_shareasale_id(), '' ),
+ 'is_admin' => is_admin(),
+ 'reports_url' => $reports_url,
+ 'authed' => $site_auth || $ms_auth,
+ 'auth_connect_url' => is_network_admin() ? network_admin_url( 'index.php?page=monsterinsights-onboarding' ) : admin_url( 'index.php?page=monsterinsights-onboarding' ),
+ 'getting_started_url' => is_multisite() ? network_admin_url( 'admin.php?page=monsterinsights_network#/about/getting-started' ) : admin_url( 'admin.php?page=monsterinsights_settings#/about/getting-started' ),
+ 'wizard_url' => is_network_admin() ? network_admin_url( 'index.php?page=monsterinsights-onboarding' ) : admin_url( 'index.php?page=monsterinsights-onboarding' ),
+ 'roles_manage_options' => monsterinsights_get_manage_options_roles(),
+ 'user_roles' => $current_user->roles,
+ 'roles_view_reports' => monsterinsights_get_option('view_reports'),
+ )
+ );
}
add_action( 'wp_enqueue_scripts', 'monsterinsights_frontend_admin_bar_scripts' );
--- a/google-analytics-for-wordpress/includes/ppc/google/class-monsterinsights-google-ads.php
+++ b/google-analytics-for-wordpress/includes/ppc/google/class-monsterinsights-google-ads.php
@@ -167,6 +167,12 @@
public function reset_experience() {
check_ajax_referer('mi-admin-nonce', 'nonce');
+ if (! current_user_can('monsterinsights_save_settings')) {
+ wp_send_json_error(array(
+ 'message' => __('You do not have permission to reset the Google Ads experience.', 'google-analytics-for-wordpress'),
+ ));
+ }
+
self::clear_data();
wp_send_json_success(array(
@@ -244,6 +250,12 @@
{
check_ajax_referer('mi-admin-nonce', 'nonce');
+ if (! current_user_can('monsterinsights_save_settings')) {
+ wp_send_json_error(array(
+ 'message' => __('You do not have permission to retrieve the Google Ads access token.', 'google-analytics-for-wordpress'),
+ ));
+ }
+
$access_token_result = $this->get_access_token();
if (is_wp_error($access_token_result)) {
--- a/google-analytics-for-wordpress/lite/assets/admin-bar/index.asset.php
+++ b/google-analytics-for-wordpress/lite/assets/admin-bar/index.asset.php
@@ -0,0 +1 @@
+<?php return array('dependencies' => array('react-jsx-runtime', 'wp-element', 'wp-i18n'), 'version' => '2285a9c504aa37d29a57');
--- a/google-analytics-for-wordpress/lite/includes/popular-posts/class-popular-posts-widget.php
+++ b/google-analytics-for-wordpress/lite/includes/popular-posts/class-popular-posts-widget.php
@@ -121,7 +121,7 @@
$html .= '<a href="' . esc_url($post['link']) . '">';
if ( ! empty( $theme_styles['image'] ) && ! empty( $post['image'] ) ) {
$html .= '<div class="monsterinsights-widget-popular-posts-image">';
- $html .= '<img src="' . esc_url($post['image']) . '" srcset=" ' . esc_attr($post['srcset']) . ' " alt="' . esc_attr( $post['title'] ) . '" />';
+ $html .= '<img src="' . esc_url($post['image']) . '" srcset=" ' . esc_attr($post['srcset']) . ' " alt="' . esc_attr( wp_strip_all_tags( $post['title'] ) ) . '" />';
$html .= '</div>';
}
$html .= '<div class="monsterinsights-widget-popular-posts-text">';
@@ -132,7 +132,7 @@
}
$html .= '<span class="monsterinsights-widget-popular-posts-title" ';
$html .= ! empty( $this->get_element_style( $theme, 'title', $atts ) ) ? 'style="' . esc_attr( $this->get_element_style( $theme, 'title', $atts ) ) . '"' : '';
- $html .= '>' . esc_html( $post['title'] ) . '</span>';
+ $html .= '>' . wp_kses_post( $post['title'] ) . '</span>';
$html .= '</div>'; // monsterinsights-widget-popular-posts-text.
$html .= '</a>';
$html .= '</li>';