Found by source-code audit of the public repository and verified on a local WordPress install running plugin version 2.0.4. No third-party or production site was tested. No CVE assigned yet.

Summary

I found that the plugin’s public REST endpoint GET /wp-json/rae/v1/post returns the full content of any post when given its ID — no login, and no check on the post’s publication status. Drafts, private posts and scheduled (future) posts are all disclosed: title, full content, excerpt, slug and author details.

An unauthenticated attacker can read content the site owner deliberately kept unpublished. Post IDs are sequential integers, so walking IDs upward from 1 harvests everything.

Root cause

The endpoint is registered with permission_callback => __return_true and the handler passes the raw post_id into get_required_post_data(), which never checks post_status. File: inc/classes/api/class-get-post.php.

register_rest_route(
    'rae/v1',
    '/post',
    [
        'method'   => 'GET',
        'callback' => [ $this, 'rest_endpoint_handler' ],
        'permission_callback' => '__return_true',   // open to the world
    ]
);

$post_data = $this->get_required_post_data( $post_id );  // no status check

The sibling endpoints in the same plugin (/posts, /posts-by-tax, /home) all filter on post_status => publish. This one is the odd one out. A second vector exists in the same handler: post_slug is resolved with get_page_by_path(), which also returns non-published posts.

Proof of concept

Step 1. In wp-admin, confirm which posts are unpublished: one Draft, one Private, one Scheduled, one Published.

image.png

Step 2. As an anonymous visitor (no cookies, no credentials), request the draft:GET /wp-json/rae/v1/post?post_id=5

Step 3. HTTP 200 — the draft’s full secret content is returned:

image.png

Step 4. Repeat for post_id=6 (private) and post_id=8 (scheduled): both return HTTP 200 with their full content. The slug vector (?post_slug=draft-secret-launch-plan) leaks the draft too.

Impact

Anyone on the internet can harvest unpublished content from sites running this plugin: embargoed launches, internal private memos, scheduled announcements. CWE-200 / CWE-862. CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N = 7.5 High.

Remediation

In get_required_post_data(), refuse anything not published unless the requester may read it:

$post = get_post( $post_ID );
if ( ! $post || 'publish' !== $post->post_status ) {
    if ( ! current_user_can( 'read_post', $post_ID ) ) {
        return []; // handler already turns this into 406 "Post not found"
    }
}