Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/wedocs/assets/build/blocks/Sidebar/render.php
+++ b/wedocs/assets/build/blocks/Sidebar/render.php
@@ -76,6 +76,61 @@
}
/**
+ * Validate a CSS length value (e.g. "1px", "0.5rem", "10%").
+ *
+ * Block attributes are stored unsanitized, so any string reaching a
+ * style attribute must be validated before output. Anything that is not
+ * a plain CSS length falls back to a safe default to prevent attribute
+ * breakouts / stored XSS.
+ *
+ * @since 2.3.1
+ *
+ * @param mixed $value The raw value to validate.
+ * @param string $fallback Fallback length when validation fails.
+ *
+ * @return string A safe CSS length value.
+ */
+if ( ! function_exists( 'wedocs_sanitize_css_length' ) ) {
+ function wedocs_sanitize_css_length( $value, $fallback = '1px' ) {
+ if ( ! is_string( $value ) && ! is_numeric( $value ) ) {
+ return $fallback;
+ }
+
+ $value = trim( (string) $value );
+
+ // Allow a bare number (treated as px-less) or number + unit.
+ if ( preg_match( '/^d+(.d+)?(px|em|rem|%|vh|vw|pt)?$/', $value ) ) {
+ return $value;
+ }
+
+ return $fallback;
+ }
+}
+
+/**
+ * Validate an HTML heading/inline tag name against a whitelist.
+ *
+ * esc_attr() is NOT a safe escaper for tag names — a value like
+ * "h3 onclick=alert(1)" survives it and breaks out into attributes.
+ * Only a strict whitelist is safe here.
+ *
+ * @since 2.3.1
+ *
+ * @param mixed $tag The raw tag name.
+ * @param string $fallback Fallback tag when not whitelisted.
+ *
+ * @return string A safe, whitelisted tag name.
+ */
+if ( ! function_exists( 'wedocs_sanitize_tag_name' ) ) {
+ function wedocs_sanitize_tag_name( $tag, $fallback = 'h3' ) {
+ $allowed = [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'span', 'p' ];
+ $tag = is_string( $tag ) ? strtolower( trim( $tag ) ) : '';
+
+ return in_array( $tag, $allowed, true ) ? $tag : $fallback;
+ }
+}
+
+/**
* Process WordPress color class and add to appropriate output
*
* @param string $parsed_color The parsed color value
@@ -134,8 +189,9 @@
$connector_width = intval( str_replace( 'px', '', $tree_styles['indentation'] ?? '20' ) ) / 2;
$connector_color = wedocs_get_color_value( $tree_styles['connectorColor'] ?? '', '#e5e7eb' );
+ $line_width = wedocs_sanitize_css_length( $tree_styles['connectorWidth'] ?? '1px', '1px' );
- return '<div class="wedocs-connector-line" style="position: absolute; left: -' . $connector_width . 'px; top: 0; bottom: 0; width: ' . ( $tree_styles['connectorWidth'] ?? '1px' ) . '; background-color: ' . esc_attr( $connector_color ) . ';"></div>';
+ return '<div class="wedocs-connector-line" style="position: absolute; left: -' . esc_attr( $connector_width ) . 'px; top: 0; bottom: 0; width: ' . esc_attr( $line_width ) . '; background-color: ' . esc_attr( $connector_color ) . ';"></div>';
}
}
if ( ! function_exists( 'render_wedocs_sidebar' ) ) {
@@ -151,8 +207,8 @@
if ($enable_nested_articles === '') {
$enable_nested_articles = true;
}
- $section_title_tag = $attributes['sectionTitleTag'] ?? 'h3';
- $article_title_tag = $attributes['articleTitleTag'] ?? 'h4';
+ $section_title_tag = wedocs_sanitize_tag_name( $attributes['sectionTitleTag'] ?? 'h3', 'h3' );
+ $article_title_tag = wedocs_sanitize_tag_name( $attributes['articleTitleTag'] ?? 'h4', 'h4' );
// Styling attributes
$container_styles = $attributes['containerStyles'] ?? [];
$section_styles = $attributes['sectionStyles'] ?? [];
@@ -443,7 +499,7 @@
if ( $level > 0 ) {
$section_style .= 'margin-left: ' . $indentation . 'px;';
}
- $section_style .= 'margin-bottom: ' . ( $tree_styles['itemSpacing'] ?? '4px' ) . ';';
+ $section_style .= 'margin-bottom: ' . wedocs_sanitize_css_length( $tree_styles['itemSpacing'] ?? '4px', '4px' ) . ';';
$section_style .= 'position: relative;';
$header_style = '';
if ( $level === 0 ) {
@@ -551,7 +607,7 @@
str_replace( 'px', '', $tree_styles['indentation'] ?? '20' )
) . 'px;';
if ( ! empty( $tree_styles['connectorColor'] ) ) {
- $children_style .= 'border-left: ' . ( $tree_styles['connectorWidth'] ?? '1px' ) . ' solid ' . esc_attr(
+ $children_style .= 'border-left: ' . wedocs_sanitize_css_length( $tree_styles['connectorWidth'] ?? '1px', '1px' ) . ' solid ' . esc_attr(
$tree_styles['connectorColor']
) . ';';
}
@@ -607,7 +663,7 @@
if ( $level > 0 ) {
$article_style .= 'margin-left: ' . $indentation . 'px;';
}
- $article_style .= 'margin-bottom: ' . ( $tree_styles['itemSpacing'] ?? '4px' ) . ';';
+ $article_style .= 'margin-bottom: ' . wedocs_sanitize_css_length( $tree_styles['itemSpacing'] ?? '4px', '4px' ) . ';';
$article_style .= 'position: relative;';
$icon_style = '';
$icon_color = wedocs_get_color_value( $doc_list_styles['textColor'] ?? '', '#6c757d' );
@@ -649,7 +705,7 @@
str_replace( 'px', '', $tree_styles['indentation'] ?? '20' )
) . 'px;';
if ( ! empty( $tree_styles['connectorColor'] ) ) {
- $children_style .= 'border-left: ' . ( $tree_styles['connectorWidth'] ?? '1px' ) . ' solid ' . esc_attr(
+ $children_style .= 'border-left: ' . wedocs_sanitize_css_length( $tree_styles['connectorWidth'] ?? '1px', '1px' ) . ' solid ' . esc_attr(
$tree_styles['connectorColor']
) . ';';
}
--- a/wedocs/assets/build/index.asset.php
+++ b/wedocs/assets/build/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => 'b61e8a2d02b8e18f27fc');
+<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '03d095925f226ea789ba');
--- a/wedocs/includes/Admin/Migrate.php
+++ b/wedocs/includes/Admin/Migrate.php
@@ -47,6 +47,34 @@
}
/**
+ * Verify a migration AJAX request is authorized.
+ *
+ * The BetterDocs->weDocs migration creates/updates posts, updates
+ * options and deactivates plugins, so it must be gated behind both a
+ * nonce check and an administrator capability check. Without these,
+ * any logged-in user (subscriber+) could trigger it.
+ *
+ * @since 2.3.1
+ *
+ * @return void Sends a JSON error and dies when unauthorized.
+ */
+ private static function verify_migration_request() {
+ if ( ! check_ajax_referer( 'wedocs-migration', 'nonce', false ) ) {
+ wp_send_json_error( [
+ 'success' => false,
+ 'message' => __( 'Security verification failed.', 'wedocs' ),
+ ], 403 );
+ }
+
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_send_json_error( [
+ 'success' => false,
+ 'message' => __( 'You are not allowed to perform this action.', 'wedocs' ),
+ ], 403 );
+ }
+ }
+
+ /**
* Check migration availability.
*
* @since 2.0.0
@@ -54,6 +82,8 @@
* @return void
*/
public static function need_migration() {
+ self::verify_migration_request();
+
// Check is betterdocs available.
if ( ! self::is_betterdocs_exists() ) {
wp_send_json_error( [
@@ -204,6 +234,8 @@
* @return void
*/
public static function do_migration() {
+ self::verify_migration_request();
+
$migratable_docs = self::betterdocs_migratable_docs();
$migrated_docs_length = ! empty( $_POST[ 'migratedDocLength' ] ) ? $_POST[ 'migratedDocLength' ] : 0;
--- a/wedocs/includes/Assets.php
+++ b/wedocs/includes/Assets.php
@@ -74,8 +74,10 @@
'aiProviderConfigs' => wedocs_get_ai_provider_configs(),
'adminUrl' => admin_url(),
'hasManageCap' => current_user_can( 'manage_options' ),
+ 'migrationNonce' => wp_create_nonce( 'wedocs-migration' ),
'weDocsUrl' => admin_url( 'admin.php?page=wedocs#/' ),
'pro_active' => wedocs_is_pro_active(),
+ 'dokan_active' => is_plugin_active( 'dokan-lite/dokan.php' ),
'upgradePopupContent' => wedocs_get_upgrade_popup_content(),
'siteUrl' => home_url( '/' ),
),
--- a/wedocs/vendor/composer/installed.php
+++ b/wedocs/vendor/composer/installed.php
@@ -1,9 +1,9 @@
<?php return array(
'root' => array(
'name' => 'tareq1988/wedocs',
- 'pretty_version' => 'v2.3.0',
- 'version' => '2.3.0.0',
- 'reference' => '547bd0e257a4ed44636c5a185e52f5f197a85abf',
+ 'pretty_version' => 'v2.3.1',
+ 'version' => '2.3.1.0',
+ 'reference' => 'dd786d13e348fb7f9732c7e71058463dbfb8367e',
'type' => 'wordpress-plugin',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -11,9 +11,9 @@
),
'versions' => array(
'tareq1988/wedocs' => array(
- 'pretty_version' => 'v2.3.0',
- 'version' => '2.3.0.0',
- 'reference' => '547bd0e257a4ed44636c5a185e52f5f197a85abf',
+ 'pretty_version' => 'v2.3.1',
+ 'version' => '2.3.1.0',
+ 'reference' => 'dd786d13e348fb7f9732c7e71058463dbfb8367e',
'type' => 'wordpress-plugin',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
--- 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.3.0
+Version: 2.3.1
Author: weDevs
Author URI: https://wedocs.co/?utm_source=wporg&utm_medium=banner&utm_campaign=author-uri
License: GPL2
@@ -60,7 +60,7 @@
*
* @var string
*/
- const VERSION = '2.3.0';
+ const VERSION = '2.3.1';
/**
* The plugin url.