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

CVE-2026-40795: Booking for Appointments and Events Calendar – Amelia <= 2.2 – Missing Authorization (ameliabooking)

Plugin ameliabooking
Severity Medium (CVSS 4.3)
CWE 862
Vulnerable Version 2.2
Patched Version 2.2.1
Disclosed April 27, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-40795:
The Booking for Appointments and Events Calendar – Amelia plugin for WordPress (versions getPermissionService()->currentUserCanWrite(Entities::SETTINGS)` and throws an `AccessDeniedException` if the check fails.

Exploitation requires an authenticated WordPress session with subscriber-level access or higher. The attacker sends a POST request to `/wp-admin/admin-ajax.php` with the `action` parameter set to the AJAX handler that invokes `ToggleFeatureIntegrationCommandHandler`. The specific action name depends on the plugin’s AJAX registration. The attacker includes parameters such as `code` to specify which feature integration to toggle (e.g., `googleCalendar`, `outlookCalendar`, `whatsapp`). No nonce or capability verification was performed in the vulnerable version.

The patch adds a capability check using `currentUserCanWrite(Entities::SETTINGS)` at the beginning of the `handle` method. If the user lacks this capability, an `AccessDeniedException` is thrown and the command fails before any state changes. The additional changes in `SettingsStorage.php` redact sensitive token values from API responses (e.g., `gMapApiKey`, `googleAccessToken`, `outlookAccessToken`, `whatsAppAccessToken`) by using `!empty()` to return boolean presence indicators instead of actual values. This prevents information disclosure.

Successful exploitation allows an attacker with subscriber-level access to enable or disable feature integrations (e.g., Google Calendar sync, Outlook Calendar sync, WhatsApp notifications) and potentially view boolean presence indicators for sensitive API keys and tokens via the settings API. While direct sensitive data exposure is mitigated by the token redaction patch, an attacker could still infer which integrations are configured and manipulate the integration state of the application.

Differential between vulnerable and patched code

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

Code Diff
--- a/ameliabooking/ameliabooking.php
+++ b/ameliabooking/ameliabooking.php
@@ -3,7 +3,7 @@
 Plugin Name: Amelia
 Plugin URI: https://wpamelia.com/
 Description: Amelia is a simple yet powerful automated booking specialist, working 24/7 to make sure your customers can make appointments and events even while you sleep!
-Version: 2.2
+Version: 2.2.1
 Author: Melograno Ventures
 Author URI: https://melograno.io/
 Text Domain: ameliabooking
@@ -111,7 +111,7 @@

 // Const for Amelia version
 if (!defined('AMELIA_VERSION')) {
-    define('AMELIA_VERSION', '2.2');
+    define('AMELIA_VERSION', '2.2.1');
 }

 // Const for site URL
--- a/ameliabooking/src/Application/Commands/Settings/FeaturesIntegrations/ToggleFeatureIntegrationCommandHandler.php
+++ b/ameliabooking/src/Application/Commands/Settings/FeaturesIntegrations/ToggleFeatureIntegrationCommandHandler.php
@@ -4,6 +4,8 @@

 use AmeliaBookingApplicationCommandsCommandHandler;
 use AmeliaBookingApplicationCommandsCommandResult;
+use AmeliaBookingApplicationCommonExceptionsAccessDeniedException;
+use AmeliaBookingDomainEntityEntities;
 use AmeliaBookingDomainServicesSettingsSettingsService;

 class ToggleFeatureIntegrationCommandHandler extends CommandHandler
@@ -16,6 +18,10 @@
     {
         $result = new CommandResult();

+        if (!$command->getPermissionService()->currentUserCanWrite(Entities::SETTINGS)) {
+            throw new AccessDeniedException('You are not allowed to write settings.');
+        }
+
         $this->checkMandatoryFields($command);

         $code = $command->getField('code');
--- a/ameliabooking/src/Infrastructure/WP/SettingsService/SettingsStorage.php
+++ b/ameliabooking/src/Infrastructure/WP/SettingsService/SettingsStorage.php
@@ -174,11 +174,10 @@
                 'timeSlotLength'                            => $this->getSetting('general', 'timeSlotLength'),
                 'serviceDurationAsSlot'                     => $this->getSetting('general', 'serviceDurationAsSlot'),
                 'defaultAppointmentStatus'                  => $this->getSetting('general', 'defaultAppointmentStatus'),
-                'gMapApiKey'                                => $this->getSetting('general', 'gMapApiKey'),
+                'gMapApiKey'                                => !empty($this->getSetting('general', 'gMapApiKey')),
                 'googleClientId'                            => $this->getSetting('googleCalendar', 'clientID'),
-                'googleAccessToken'                         => $this->getSetting('googleCalendar', 'accessToken'),
+                'googleAccessToken'                         => !empty($this->getSetting('googleCalendar', 'accessToken')),
                 'googleAccountData'                         => $this->getSetting('googleCalendar', 'googleAccountData'),
-                'outlookAccessToken'                        => $this->getSetting('outlookCalendar', 'accessToken'),
                 'outlookAccountData'                        => $this->getSetting('outlookCalendar', 'outlookAccountData'),
                 'addToCalendar'                             => $this->getSetting('general', 'addToCalendar'),
                 'requiredPhoneNumberField'                  => $this->getSetting('general', 'requiredPhoneNumberField'),
@@ -250,7 +249,7 @@
                     $this->getSetting('featuresIntegrations', 'googleCalendar')
                 ),
                 'googleMeetEnabled' => $this->getSetting('googleCalendar', 'enableGoogleMeet'),
-                'accessToken' => $this->getSetting('googleCalendar', 'accessToken'),
+                'accessToken' => !empty($this->getSetting('googleCalendar', 'accessToken')),
             ],
             'outlookCalendar'        => [
                 'enabled'               =>
@@ -267,7 +266,7 @@
                         $this->getSetting('featuresIntegrations', 'outlookCalendar')
                     ),
                 'microsoftTeamsEnabled' => $this->getSetting('outlookCalendar', 'enableMicrosoftTeams'),
-                'accessToken' => $this->getSetting('outlookCalendar', 'accessToken'),
+                'accessToken' => !empty($this->getSetting('outlookCalendar', 'accessToken')),
             ],
             'appleCalendar'          =>
             $this->getSetting('appleCalendar', 'clientID') && $this->getSetting('appleCalendar', 'clientSecret'),
@@ -355,14 +354,13 @@
                 'bccEmail'            => $this->getSetting('notifications', 'bccEmail'),
                 'bccSms'              => $this->getSetting('notifications', 'bccSms'),
                 'smsBalanceEmail'     => $this->getSetting('notifications', 'smsBalanceEmail'),
-                'whatsAppPhoneID'     => $this->getSetting('notifications', 'whatsAppPhoneID'),
-                'whatsAppAccessToken' => $this->getSetting('notifications', 'whatsAppAccessToken'),
-                'whatsAppBusinessID'  => $this->getSetting('notifications', 'whatsAppBusinessID'),
-                'whatsAppLanguage'    => $this->getSetting('notifications', 'whatsAppLanguage'),
                 'whatsAppEnabled'     => LicenceLicence::isFeatureEnabledWithLicense(
                     'whatsapp',
                     $this->getSetting('featuresIntegrations', 'whatsapp')
-                ),
+                ) &&
+                !empty($this->getSetting('notifications', 'whatsAppPhoneID')) &&
+                !empty($this->getSetting('notifications', 'whatsAppAccessToken')) &&
+                !empty($this->getSetting('notifications', 'whatsAppBusinessID')),
             ],
             'payments'               => [
                 'currency'                   => $this->getSetting('payments', 'symbol'),
@@ -426,9 +424,6 @@
                     'clientLiveId'   => $this->getSetting('payments', 'square')['clientLiveId'],
                     'clientTestId'   => $this->getSetting('payments', 'square')['clientTestId'],
                     'testMode'       => $this->getSetting('payments', 'square')['testMode'],
-                    'accessTokenSet' =>
-                    !empty($this->getSetting('payments', 'square')['accessToken']) &&
-                        !empty($this->getSetting('payments', 'square')['accessToken']['access_token']),
                     'locationId'     => $this->getSetting('payments', 'square')['locationId']
                 ],
                 'razorpay'                   => [
@@ -436,10 +431,6 @@
                 ],
                 'barion'                     => [
                     'enabled'       => $this->getSetting('payments', 'barion')['enabled'],
-                    'sandboxMode'   => $this->getSetting('payments', 'barion')['sandboxMode'],
-                    'livePOSKey'    => $this->getSetting('payments', 'barion')['livePOSKey'],
-                    'sandboxPOSKey' => $this->getSetting('payments', 'barion')['sandboxPOSKey'],
-                    'payeeEmail'    => $this->getSetting('payments', 'barion')['payeeEmail'],
                 ],
             ],
             'role'                   => $userType,
--- a/ameliabooking/src/Infrastructure/WP/ShortcodeService/AmeliaBookingShortcodeService.php
+++ b/ameliabooking/src/Infrastructure/WP/ShortcodeService/AmeliaBookingShortcodeService.php
@@ -108,7 +108,7 @@
         } else {
             wp_enqueue_script(
                 $scriptId,
-                AMELIA_URL . 'v3/public/assets/public.4c4ba0fe.js',
+                AMELIA_URL . 'v3/public/assets/public.eda3778f.js',
                 [],
                 AMELIA_VERSION,
                 true
--- a/ameliabooking/src/Infrastructure/WP/WPMenu/SubmenuPageHandler.php
+++ b/ameliabooking/src/Infrastructure/WP/WPMenu/SubmenuPageHandler.php
@@ -148,7 +148,7 @@
         } else {
             wp_enqueue_script(
                 $scriptId,
-                AMELIA_URL . 'v3/public/assets/admin.51330dd3.js',
+                AMELIA_URL . 'v3/public/assets/admin.daae4f75.js',
                 [],
                 AMELIA_VERSION,
                 true
--- a/ameliabooking/vendor/autoload.php
+++ b/ameliabooking/vendor/autoload.php
@@ -22,4 +22,4 @@

 require_once __DIR__ . '/composer/autoload_real.php';

-return ComposerAutoloaderInit11c06463a871b4da1d620537a06e6d15::getLoader();
+return ComposerAutoloaderInit7de21991dccf3f6176ff7feffc90474c::getLoader();
--- a/ameliabooking/vendor/composer/autoload_real.php
+++ b/ameliabooking/vendor/composer/autoload_real.php
@@ -2,7 +2,7 @@

 // autoload_real.php @generated by Composer

-class ComposerAutoloaderInit11c06463a871b4da1d620537a06e6d15
+class ComposerAutoloaderInit7de21991dccf3f6176ff7feffc90474c
 {
     private static $loader;

@@ -24,16 +24,16 @@

         require __DIR__ . '/platform_check.php';

-        spl_autoload_register(array('ComposerAutoloaderInit11c06463a871b4da1d620537a06e6d15', 'loadClassLoader'), true, true);
+        spl_autoload_register(array('ComposerAutoloaderInit7de21991dccf3f6176ff7feffc90474c', 'loadClassLoader'), true, true);
         self::$loader = $loader = new ComposerAutoloadClassLoader(dirname(__DIR__));
-        spl_autoload_unregister(array('ComposerAutoloaderInit11c06463a871b4da1d620537a06e6d15', 'loadClassLoader'));
+        spl_autoload_unregister(array('ComposerAutoloaderInit7de21991dccf3f6176ff7feffc90474c', 'loadClassLoader'));

         require __DIR__ . '/autoload_static.php';
-        call_user_func(ComposerAutoloadComposerStaticInit11c06463a871b4da1d620537a06e6d15::getInitializer($loader));
+        call_user_func(ComposerAutoloadComposerStaticInit7de21991dccf3f6176ff7feffc90474c::getInitializer($loader));

         $loader->register(true);

-        $filesToLoad = ComposerAutoloadComposerStaticInit11c06463a871b4da1d620537a06e6d15::$files;
+        $filesToLoad = ComposerAutoloadComposerStaticInit7de21991dccf3f6176ff7feffc90474c::$files;
         $requireFile = Closure::bind(static function ($fileIdentifier, $file) {
             if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
                 $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
--- a/ameliabooking/vendor/composer/autoload_static.php
+++ b/ameliabooking/vendor/composer/autoload_static.php
@@ -4,7 +4,7 @@

 namespace ComposerAutoload;

-class ComposerStaticInit11c06463a871b4da1d620537a06e6d15
+class ComposerStaticInit7de21991dccf3f6176ff7feffc90474c
 {
     public static $files = array (
         '7b11c4dc42b3b3023073cb14e519683c_am' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
@@ -11741,10 +11741,10 @@
     public static function getInitializer(ClassLoader $loader)
     {
         return Closure::bind(function () use ($loader) {
-            $loader->prefixLengthsPsr4 = ComposerStaticInit11c06463a871b4da1d620537a06e6d15::$prefixLengthsPsr4;
-            $loader->prefixDirsPsr4 = ComposerStaticInit11c06463a871b4da1d620537a06e6d15::$prefixDirsPsr4;
-            $loader->prefixesPsr0 = ComposerStaticInit11c06463a871b4da1d620537a06e6d15::$prefixesPsr0;
-            $loader->classMap = ComposerStaticInit11c06463a871b4da1d620537a06e6d15::$classMap;
+            $loader->prefixLengthsPsr4 = ComposerStaticInit7de21991dccf3f6176ff7feffc90474c::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInit7de21991dccf3f6176ff7feffc90474c::$prefixDirsPsr4;
+            $loader->prefixesPsr0 = ComposerStaticInit7de21991dccf3f6176ff7feffc90474c::$prefixesPsr0;
+            $loader->classMap = ComposerStaticInit7de21991dccf3f6176ff7feffc90474c::$classMap;

         }, null, ClassLoader::class);
     }

ModSecurity Protection Against This CVE

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

ModSecurity
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" "id:20264079,phase:2,deny,status:403,chain,msg:'CVE-2026-40795 Amelia ToggleFeatureIntegration missing auth',severity:'CRITICAL',tag:'CVE-2026-40795'"
  SecRule ARGS_POST:action "@rx ^toggleFeatureIntegration$" "chain"
    SecRule ARGS_POST:code "@rx ^(?:googleCalendar|outlookCalendar|whatsapp|zoom|appleCalendar)$" "t:none"

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