CVE-2026-4019: Unauthenticated Private Post Content Disclosure In Complianz Plugin
Table of Contents
CVE-2026-4019 is a CVSS 5.3 Medium missing authorization vulnerability in the Complianz – GDPR/CCPA Cookie Consent WordPress plugin. Unauthenticated attackers can read private, draft, or unpublished post content through a publicly accessible REST API endpoint.
Vulnerability Summary
| Field | Value |
|---|---|
| Plugin Name | Complianz – GDPR/CCPA Cookie Consent |
| Plugin Slug | complianz-gdpr |
| CVE ID | CVE-2026-4019 |
| CVSS Score | 5.3 (Medium) |
| Vulnerability Type | Missing Authorization to Unauthenticated Private Post Content Disclosure via Consent Area REST Endpoint |
| Affected Versions | <= 7.4.5 |
| Patched Version | 7.4.6 |
| Published | April 28, 2026 |
| Researcher | Wesley van de Kamp - Conda Security |
| Wordfence Advisory | Link |
Description
Unauthenticated attackers can read private, draft, or unpublished WordPress post content through a publicly accessible REST API endpoint. The Complianz plugin registers a route at /wp-json/complianz/v1/consent-area/{post_id}/{block_id} that returns the hidden content of a complianz/consent-area block. The endpoint does not check whether the requesting user has permission to view the post. Because of this, anyone on the internet can retrieve sensitive content that site owners intended to hide behind consent requirements.
In practice, the consent-area block is designed to show content only after a visitor accepts a specific cookie category. Site owners often place marketing scripts, embedded media, or other restricted content inside it. However, the REST endpoint bypasses this protection entirely for posts that are not publicly visible.
Technical Analysis
Vulnerable Code Path
The cmplz_documents_rest_route() function in rest-api/rest-api.php registers the vulnerable endpoint on line 51:
register_rest_route( 'complianz/v1', 'consent-area/(?P<post_id>'.$id_pattern.')/(?P<block_id>'.$string_pattern.')', array(
'methods' => 'GET',
'callback' => 'cmplz_rest_consented_content',
'permission_callback' => '__return_true',
) );
The permission_callback is set to __return_true. This tells WordPress to allow the request without any authentication or authorization check.
The callback function cmplz_rest_consented_content() starts on line 61 of the same file:
function cmplz_rest_consented_content( WP_REST_Request $request ) {
$post_id = (int) ($request->get_param('post_id'));
$block_id = sanitize_title($request->get_param('block_id'));
$post = get_post($post_id);
if ( !$post ) {
return '';
}
$html = $post->post_content;
$output = '';
if ( has_block('complianz/consent-area', $html)) {
$blocks = parse_blocks($post->post_content);
foreach($blocks as $block){
if ($block['blockName']==='complianz/consent-area' && $block['attrs']['blockId']===$block_id){
$output = $block['attrs']['consentedContent'];
break;
}
}
}
// ... shortcode handling omitted for brevity
$output = do_shortcode($output);
return $output;
}
The function retrieves the post with get_post($post_id). It then parses the post content for a complianz/consent-area block. When it finds a matching block, it returns the consentedContent attribute directly. This attribute stores the HTML content that the site owner wants to hide until the visitor gives consent.
Root Cause
The function does not verify the post’s post_status or the current user’s permission to read the post before returning the block content.
Why Existing Controls Failed
The REST route relies entirely on __return_true as its permission callback. WordPress never runs any capability check. Because of this, the endpoint skips WordPress’s built-in post visibility protections. The get_post() function itself does not filter by status or user permissions — it fetches the post from the database.
Attack Impact
In the worst case, an attacker can read the full consentedContent of consent-area blocks from any post on the site. This includes private posts, drafts, and pending reviews. It may also expose embedded media, marketing tracking scripts, contact forms, or other content that the site owner assumed was protected.
Proof of Concept
Disclaimer: This PoC is provided for educational and defensive security research purposes only.
Prerequisites
- WordPress installation with the
complianz-gdprplugin installed and activated - Plugin version <= 7.4.5
- A post (private, draft, or published) that contains a
complianz/consent-areablock with a knownblockId
Step-by-Step Reproduction
Step 1: Identify a target post and block ID
Create a private post and add a Complianz consent-area block. Note the post ID from the WordPress editor URL. Inspect the block attributes or the front-end HTML to find the data-block_id value. For example, the block might render as:
<div class="cmplz-consent-area cmplz-placeholder" data-post_id="42" data-block_id="my-block" ...>
In this example:
post_id=42block_id=my-block
Step 2: Send an unauthenticated request to the REST endpoint
Run the following curl command from any machine with network access to the WordPress site:
curl -s "https://example.com/wp-json/complianz/v1/consent-area/42/my-block"
Replace example.com with the target domain, 42 with the target post ID, and my-block with the target block ID.
Expected Result
The server returns the raw HTML stored in the block’s consentedContent attribute. The server returns this content even though the post is private and the request is unauthenticated.
Verification
Confirm the exploit succeeded by checking that:
- The HTTP response code is
200 - The response body contains the hidden HTML that was placed inside the consent-area block
- The same request returns an empty string or error when a non-existent block ID is used
Patch Analysis
What Changed
rest-api/rest-api.php— The patch adds a post-status and capability check at the start ofcmplz_rest_consented_content().
Fix Explanation
To close the gap, the patch checks the post’s visibility before returning any content. It adds these lines immediately after the existing $post = get_post($post_id); check:
if ( 'publish' !== $post->post_status && ! current_user_can( 'read_post', $post->ID ) ) {
return new WP_Error( 'rest_forbidden', '', array( 'status' => 403 ) );
}
This check works in two stages. First, it allows the request if the post status is publish. Second, if the post is not published, it checks whether the current user has the read_post capability for that specific post. Unauthenticated visitors do not have this capability for private or draft posts. As a result, the endpoint now returns a 403 Forbidden error for any post that the user is not allowed to read.
The permission callback on the route itself remains __return_true. This is acceptable. The authorization logic has moved into the callback function, where it can evaluate the specific post being requested.
Code Diff (Key Changes)
function cmplz_rest_consented_content( WP_REST_Request $request ) {
- $post_id = (int) ($request->get_param('post_id'));
- $block_id = sanitize_title($request->get_param('block_id'));
- $post = get_post($post_id);
+ $post_id = (int) ( $request->get_param( 'post_id' ) );
+ $block_id = sanitize_title( $request->get_param( 'block_id' ) );
+ $post = get_post( $post_id );
- if ( !$post ) {
+ if ( ! $post ) {
return '';
}
- $html = $post->post_content;
+ if ( 'publish' !== $post->post_status && ! current_user_can( 'read_post', $post->ID ) ) {
+ return new WP_Error( 'rest_forbidden', '', array( 'status' => 403 ) );
+ }
+
+ $html = $post->post_content;
$output = '';
- if ( has_block('complianz/consent-area', $html)) {
- $blocks = parse_blocks($post->post_content);
- foreach($blocks as $block){
- if ($block['blockName']==='complianz/consent-area' && $block['attrs']['blockId']===$block_id){
+ if ( has_block( 'complianz/consent-area', $html ) ) {
+ $blocks = parse_blocks( $post->post_content );
+ foreach ( $blocks as $block ) {
+ if ( 'complianz/consent-area' === $block['blockName'] && $block['attrs']['blockId'] === $block_id ) {
$output = $block['attrs']['consentedContent'];
break;
}
}
- } else if ( strpos($html, '[cmplz-consent-area')!==false ) {
+ } elseif ( strpos( $html, '[cmplz-consent-area' ) !== false ) {
Timeline
| Date | Event |
|---|---|
| April 28, 2026 | Vulnerability publicly disclosed |
| April 28, 2026 | Patched version 7.4.6 released |
Remediation
Update the complianz-gdpr plugin to version 7.4.6 or later.
References
- Wordfence Advisory
- CVE-2026-4019 Record
- Vulnerable code on GitHub (rest-api.php line 61)
- Plugins Trac - vulnerable version 7.4.4.2 rest-api.php line 54
- Plugins Trac - vulnerable version 7.4.4.2 rest-api.php line 61
- Plugins Trac - changeset 3508713 (patch)
- Plugins Trac - diff between 7.4.5 and 7.4.6
Frequently Asked Questions
What is CVE-2026-4019?
CVE-2026-4019 is a CVSS 5.3 Medium missing authorization vulnerability in the Complianz plugin that allows unauthenticated attackers to read private, draft, or unpublished WordPress post content through a publicly accessible REST API endpoint.
Which versions of Complianz – GDPR/CCPA Cookie Consent are affected by CVE-2026-4019?
All versions of the Complianz plugin up to and including 7.4.5 are affected. Version 7.4.6 contains the fix and is safe to use.
What can an attacker do with CVE-2026-4019?
An attacker can send a simple HTTP request to a REST endpoint and retrieve the hidden content of any consent-area block, even on private or draft posts. This may expose embedded media, marketing scripts, contact forms, or other content the site owner intended to protect.
Does an attacker need to be logged in to exploit CVE-2026-4019?
No. The vulnerable endpoint has no authentication requirement. Any visitor on the internet can send the request and read the protected content without any account or credentials.
How do I fix CVE-2026-4019 in Complianz – GDPR/CCPA Cookie Consent?
Update the Complianz plugin to version 7.4.6 or later. You can do this from the WordPress admin dashboard under Plugins, or by downloading the latest version from the WordPress plugin directory.
Has Complianz – GDPR/CCPA Cookie Consent been patched for CVE-2026-4019?
Yes. The developers released version 7.4.6 on April 28, 2026, which adds a post-status and capability check to the vulnerable REST endpoint before returning any content.