CVE-2026-11380: Verified Repro With Script Download
CVE-2026-11380: JetWidgets For Elementor Stored XSS via Animated Box animation effect
CVE-2026-11380 is verified against the affected target. Vulnerability class: XSS. This medium reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00203.
What Is CVE-2026-11380?
CVE-2026-11380 is a medium-severity stored cross-site scripting (XSS) vulnerability in the JetWidgets For Elementor WordPress plugin (versions up to and including 1.0.21), in the Animated Box widget's animation_effect setting. Pruva reproduced it (reproduction REPRO-2026-00203).
CVE-2026-11380 Severity & CVSS Score
CVE-2026-11380 is rated medium severity, with a CVSS base score of 6.4 out of 10.
Medium — meaningful risk under specific conditions. Schedule a fix in the normal cycle.
How to Reproduce CVE-2026-11380
pruva-verify REPRO-2026-00203 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00203/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-11380
- reached the target end-to-end
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
animation_effect widget setting containing a double-quote that closes the class attribute and injects onmouseover/style attributes
- Published WordPress page rendered by Elementor + JetWidgets Animated Box widget
Stored XSS bypass in JetWidgets For Elementor 1.0.22: the Pricing Table widget's features_list/item_text setting is rendered raw through __loop_item() without escaping, allowing an author to inject <script> tags that execute for any page visitor.
How the agent worked
Root Cause and Exploit Chain for CVE-2026-11380
The JetWidgets For Elementor WordPress plugin (versions up to and including 1.0.21) is vulnerable to a stored cross-site scripting (Stored XSS) flaw in the Animated Box widget. The widget's animation_effect setting is printed directly into the HTML class attribute of the rendered widget without output escaping or server-side validation. An attacker who can supply a value containing a double quote can break out of the class attribute and inject arbitrary attributes, including event handlers such as onmouseover="alert(1)". Because the payload is stored in the page and rendered whenever anyone visits the page, this is a stored XSS issue. The reproduction confirmed the payload in the published page HTML by deploying a real WordPress + Elementor + JetWidgets stack and viewing the rendered page.
- Product / component:
jetwidgets-for-elementor(Crocoblock / jetmonsters) — specifically thejw-animated-boxwidget. - Affected versions: 1.0.21 and earlier (the WordPress.org repository still lists 1.0.21 as the current stable release at the time of the CVE).
- Risk level: Medium. The vendor/Wordfence CVSS is reported as 6.4.
- Consequences: Any authenticated user with at least the
authorrole can save a page containing a malicious Animated Box widget. The injected JavaScript is stored in the page and executes in the browser of any visitor who views the page.
Impact Parity
- Disclosed / claimed maximum impact: Stored XSS via an author-level account in the Animated Box widget's
animation_effectsetting. - Reproduced impact from this run: A real WordPress environment was deployed with Elementor and JetWidgets 1.0.21. A published page was created with a malicious
animation_effectvalue. The rendered page HTML contains the escaped attribute boundary break and the injectedstyleandonmouseover="alert(1)"attributes. - Parity:
full— the reproduction reaches the same rendered-page sink described in the advisory. - Not demonstrated: The reproduction does not run a full browser engine, so the JavaScript payload itself is not executed in the test environment. The proof relies on the presence of the injected event handler in the server-rendered HTML, which is the root cause of the stored XSS.
Root Cause
The vulnerability is in the widget's render template:
<!-- templates/jw-animated-box/global/index.php (v1.0.21) -->
<div class="jw-animated-box <?php $this->__html( 'animation_effect', '%s' ); ?>">
The helper method chain ends up in includes/base/class-jet-widgets-base.php:
public function __render_html( $setting = null, $format = '%s' ) {
...
printf( wp_kses_post( $format ), $val );
}
wp_kses_post() is applied to the format string '%s', not to the value. Therefore the raw animation_effect value is printed verbatim inside the HTML class attribute. The UI control in includes/addons/jet-widgets-animated-box.php is a <select> with a fixed set of animation effects, but the server never validates the stored value before rendering it. A malicious author can supply a value such as:
jw-box-effect-1" style="position:fixed;width:100%;height:100%;top:0;left:0" onmouseover="alert(1)" data-x="
The first double quote closes the class attribute, the injected style covers the viewport, and the onmouseover handler will execute when a visitor moves the mouse over the page.
The vendor fixed the issue in commit 49952dd92b2bbd59e6627e7b67b0b3621c3852a0 (released as 1.0.22). The fix:
- Introduces a server-side
sanitize_animation_effect()method that rejects any value not in the allowed list of animation effects and falls back to the default. - Updates the template to use
esc_attr()on the sanitized value:
$animation_effect = $this->sanitize_animation_effect( $this->get_settings_for_display( 'animation_effect' ) );
?>
<div class="jw-animated-box <?php echo esc_attr( $animation_effect ); ?>">
Reproduction Steps
The full reproduction is automated by bundle/repro/reproduction_steps.sh. At a high level the script:
- Creates a Docker network and starts a
mysql:8.0container and awordpress:6.7-php8.1-apachecontainer. - Waits until the WordPress install page is reachable inside the Docker network.
- Installs WP-CLI, completes WordPress core installation, and sets the site URL to the internal Docker hostname (
http://jetwidgets-repro-wp). - Installs and activates Elementor 3.27.5 and JetWidgets For Elementor 1.0.21.
- Creates an
authorrole user. - Runs a PHP script inside the WordPress container that inserts a published page containing an Elementor
jw-animated-boxwidget whoseanimation_effectsetting is the malicious payload. - Fetches the rendered page from inside the Docker network using
curland copies the body and headers tobundle/repro/artifacts/. - Checks that the rendered HTML contains
onmouseover="alert(1)"and the injectedstyleattribute.
Expected evidence of reproduction: bundle/repro/artifacts/page.html contains a line similar to:
<div class="elementor-jw-animated-box jet-widgets"><div class="jw-animated-box jw-box-effect-1" style="position:fixed;width:100%;height:100%;top:0;left:0" onmouseover="alert(1)" data-x="">
Evidence
bundle/logs/reproduction_steps.log— step-by-step console output from the reproduction run.bundle/logs/insert.log— page ID and permalink produced by the PHP insertion script.bundle/logs/wordpress.log— Apache/WordPress container logs.bundle/logs/mysql.log— MySQL container logs.bundle/repro/artifacts/page.html— full HTML body of the rendered published page.bundle/repro/artifacts/page.headers— HTTP response headers from the page request.bundle/repro/artifacts/insert_page.php— the PHP helper used to create the malicious page.
Key excerpt from bundle/repro/artifacts/page.html (line 229 in the reproduced output):
<div class="elementor-jw-animated-box jet-widgets"><div class="jw-animated-box jw-box-effect-1" style="position:fixed;width:100%;height:100%;top:0;left:0" onmouseover="alert(1)" data-x="">
This shows that the animation_effect value broke out of the class attribute and added new attributes, including the onmouseover event handler.
Environment details captured in the runtime manifest:
- WordPress 6.7 (PHP 8.1 / Apache)
- Elementor 3.27.5
- JetWidgets For Elementor 1.0.21
- MySQL 8.0
Recommendations / Next Steps
- Upgrade: Site owners should upgrade JetWidgets For Elementor to version 1.0.22 or later, which contains commit
49952dd92b2bbd59e6627e7b67b0b3621c3852a0. - Fix approach:
- Server-side validation: reject any
animation_effectvalue that is not in the defined list of allowed effects (e.g.,jw-box-effect-1throughjw-box-effect-8). - Output escaping: render the value with
esc_attr()before placing it inside the HTMLclassattribute.
- Server-side validation: reject any
- Testing recommendations:
- Add unit/integration tests that save payloads containing
",<,>, andjavascript:in theanimation_effectsetting and assert that the rendered HTML does not contain an unescaped attribute break or event handler. - Test with the Elementor editor save flow as well as direct post-meta injection to ensure both paths are sanitized.
- Add unit/integration tests that save payloads containing
- Further verification: The script could be extended to include a real browser engine (e.g., Playwright/Chromium) to demonstrate actual JavaScript execution on mouseover. The current proof is sufficient to confirm the server-side sink but not a live browser exploit.
Additional Notes
- Idempotency: The reproduction script was executed twice consecutively from a clean state and succeeded both times. It cleans up all Docker containers and networks on exit via a
trap. - Limitations: The reproduction does not exercise the Elementor editor UI or the
admin-ajax.phpsave endpoint. It sets the widget data directly in post meta. This is acceptable for proving the rendered-page vulnerability sink, but it does not reproduce the full author-workflow UI. The root cause is the same regardless of how the malicious value reaches the database. - No custom exploit code: The reproduction uses the real WordPress, Elementor, and JetWidgets plugin code; no reimplementation of the vulnerability was used.
Variant Analysis & Alternative Triggers for CVE-2026-11380
The JetWidgets For Elementor 1.0.22 release was intended to fix CVE-2026-11380, a stored XSS in the Animated Box widget's animation_effect setting. The patch validates the animation_effect value against an allow-list and escapes it with esc_attr() before rendering it in the HTML class attribute. However, the patch does not address the same underlying pattern in other widgets. Specifically, the Pricing Table widget's features_list repeater still renders the item_text value through __loop_item() without any sanitization, and the final printf() does not escape the value. As a result, an authenticated author can store a feature item containing <script>alert(1)</script> and that script will be rendered verbatim on the published page. This variant reproduces on the patched version 1.0.22, making it a true bypass of the 1.0.22 fix.
Fix Coverage / Assumptions
The 1.0.22 fix relies on the assumption that the vulnerability is limited to enumerated SELECT controls whose values are rendered into HTML class attributes. The patch explicitly covers:
- Animated Box
animation_effect(includes/addons/jet-widgets-animated-box.php+templates/jw-animated-box/global/index.php) - Animated Box
button_icon_position(allow-listed) - Pricing Table
button_sizeandbutton_icon_position(templates/jw-pricing-table/global/button.php+includes/addons/jet-widgets-pricing-table.php)
The fix does not cover:
- The shared
__html()/__get_html()/__loop_item()helper methods, which still print raw stored values by default. - Other Pricing Table templates such as
features-loop-item.php,heading.php,price.php, andaction.phpthat use these helpers for text-like content. - Any other widget template that calls
__loop_item()without a sanitize callback.
Variant / Alternate Trigger
Entry point: A published WordPress page created by an author-level user containing a JetWidgets Pricing Table (
jw-pricing-table) widget.Attacker-controlled input: The
item_textfield of thefeatures_listrepeater in the Pricing Table widget settings.Data path: The malicious string is stored in Elementor post meta (
_elementor_data) and rendered when any visitor views the page.Sink:
templates/jw-pricing-table/global/features-loop-item.php:printf( '<span class="pricing-feature__text">%s</span>', $this->__loop_item( array( 'item_text' ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscapedHelper involved:
Jet_Widgets_Base::__loop_item()→Jet_Widgets_Base::__render_loop_item()inincludes/base/class-jet-widgets-base.php. The helper applies a sanitizer only if the caller provides one; otherwise it returnssprintf( $format, $value )with the raw value.Why this is a bypass: This file is unchanged between 1.0.21 and 1.0.22. The 1.0.22 patch only fixes the Animated Box and the Pricing Table button controls, leaving the feature-text path open.
Product / component:
jetwidgets-for-elementor(Crocoblock / jetmonsters), specifically thejw-pricing-tablewidget and thefeatures_listrepeater.Affected versions: 1.0.21 (vulnerable) and 1.0.22 (still vulnerable to this variant). 1.0.22 is the latest release at the time of analysis.
Risk level: Medium (same as the parent CVE: stored XSS via author-level access).
Consequences: Any authenticated user with at least the
authorrole can save a page containing a malicious Pricing Table feature. The injected JavaScript is stored in the page and executes in the browser of any visitor who views the page.
Impact Parity
- Disclosed / claimed maximum impact for the parent CVE: Stored XSS via an author-level account in the Animated Box widget's
animation_effectsetting. - Reproduced impact from this variant run: A real WordPress environment was deployed with Elementor 3.27.5 and JetWidgets 1.0.22. A published page was created with a malicious Pricing Table
item_textvalue. The rendered page HTML contains the raw<script>alert(1)</script>inside the feature text span. - Parity:
full— the variant reaches the same stored XSS impact class (author → stored script → viewer execution) through a different widget and setting. - Not demonstrated: We did not demonstrate a different impact class (e.g., code execution on the server). The reproduced impact is the same as the parent claim.
Root Cause
The root cause is the same as the parent CVE: a widget setting that is intended to be plain text is printed into the page without output escaping or server-side validation. The original fix added allow-list validation and esc_attr() only for the specific Animated Box control and two Pricing Table button controls. Because the underlying __loop_item() helper does not escape by default and the features-loop-item.php template does not call esc_html() or wp_kses_post() on the item_text value, the same stored XSS can be reached from a different entry point.
The vendor's fix commit referenced for the parent CVE is 49952dd92b2bbd59e6627e7b67b0b3621c3852a0 (released as 1.0.22). The tested 1.0.22 source is WordPress.org SVN tag tags/1.0.22, revision 3594346.
Reproduction Steps
The full reproduction is automated by bundle/vuln_variant/reproduction_steps.sh. At a high level the script:
- Spins up two isolated Docker stacks (WordPress 6.7 + PHP 8.1 + MySQL 8.0 + Elementor 3.27.5).
- Installs JetWidgets For Elementor 1.0.21 in the first stack and 1.0.22 in the second stack.
- Creates an author-level user in each stack.
- Runs a PHP script inside each WordPress container that inserts a published page containing a Pricing Table widget with a malicious
features_listitem:'features_list' => array( array( 'item_text' => '<script>alert(1)</script>', 'item_included' => 'item-included', ), ), - Fetches the rendered page from inside each Docker network and copies the body and headers to
bundle/vuln_variant/artifacts/. - Checks whether the raw
<script>alert(1)</script>string appears in each rendered page. - Reports
BYPASS CONFIRMEDand exits 0 if the payload appears in the 1.0.22 rendered page.
Expected evidence of reproduction: bundle/vuln_variant/artifacts/page_fixed.html contains a line similar to:
<div class="pricing-feature__inner"><span class="jet-widgets-icon item-bullet">...</span><span class="pricing-feature__text"><script>alert(1)</script></span></div>
The same payload appears in bundle/vuln_variant/artifacts/page_vuln.html for the 1.0.21 stack.
Evidence
bundle/logs/vuln_variant/variant_reproduction.log— step-by-step console output from the variant run (both 1.0.21 and 1.0.22).bundle/logs/vuln_variant/insert_vuln.logandbundle/logs/vuln_variant/insert_fixed.log— page IDs and permalinks produced by the PHP insertion scripts.bundle/logs/vuln_variant/wordpress_vuln.log/mysql_vuln.log— container logs for the vulnerable stack.bundle/logs/vuln_variant/wordpress_fixed.log/mysql_fixed.log— container logs for the patched stack.bundle/vuln_variant/artifacts/page_vuln.htmlandbundle/vuln_variant/artifacts/page_vuln.headers— rendered page from the 1.0.21 stack.bundle/vuln_variant/artifacts/page_fixed.htmlandbundle/vuln_variant/artifacts/page_fixed.headers— rendered page from the 1.0.22 stack.bundle/vuln_variant/artifacts/insert_vuln.phpandbundle/vuln_variant/artifacts/insert_fixed.php— the PHP helpers used to create the malicious pages.
Key excerpt from bundle/vuln_variant/artifacts/page_fixed.html (line 232 in the reproduced output):
<div class="pricing-feature__inner"><span class="jet-widgets-icon item-bullet"><svg aria-hidden="true" class="e-font-icon-svg e-fas-check" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg></span><span class="pricing-feature__text"><script>alert(1)</script></span></div>
Environment details:
- WordPress 6.7 (PHP 8.1 / Apache)
- Elementor 3.27.5
- JetWidgets For Elementor 1.0.21 (vulnerable) and 1.0.22 (patched, but bypassed)
- MySQL 8.0
Recommendations / Next Steps
Extend the fix to the Pricing Table feature text: In
templates/jw-pricing-table/global/features-loop-item.php, escape theitem_textvalue before printing. Since the control is a plain text field, the appropriate fix is:printf( '<span class="pricing-feature__text">%s</span>', esc_html( $this->__loop_item( array( 'item_text' ) ) ) );Audit all remaining
__html()/__loop_item()calls: Walk every widget template and helper call to ensure stored values are escaped according to their output context. Pay special attention to text content that should useesc_html()and URLs that should useesc_url().Harden the base helpers: Consider making
__html(),__get_html(), and__loop_item()apply context-aware escaping by default, with an explicitfalseopt-out for callers that have already escaped. This prevents the same pattern from recurring every time a new control is added.Regression tests: Add tests that save payloads containing
<script>,",>, andjavascript:into each widget's text-like controls and assert that the rendered page escapes them. Test both the Elementor editor save flow and direct post-meta injection.
Additional Notes
- Idempotency: The reproduction script was executed twice consecutively from a clean state and produced the bypass both times.
- Clean state: The script creates fresh Docker containers and networks for each run, then removes them via a
trapon exit. It does not mutate the repro stage's artifacts or the plugin source checked out inbundle/artifacts/. - No custom exploit code: The reproduction uses the real WordPress, Elementor, and JetWidgets plugin code; no reimplementation of the vulnerability was used.
- Limitations: The reproduction does not exercise the Elementor editor UI or the
admin-ajax.phpsave endpoint. It sets the widget data directly in post meta. This is acceptable for proving the rendered-page vulnerability sink, but it does not reproduce the full author-workflow UI. The root cause is the same regardless of how the malicious value reaches the database. - Trust boundary: The input is supplied by an authenticated author and rendered to anonymous visitors. This crosses the same trust boundary as the parent CVE and is within the plugin's stated security scope (the changelog repeatedly documents "sanitize & escape output" fixes).
- Not a documented limitation: The
item_textcontrol is registered as a plain TEXT control, not a WYSIWYG/HTML control. Rendering raw HTML tags from it is therefore not expected behavior.
CVE-2026-11380 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
Artifacts and Evidence for CVE-2026-11380
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-11380
FAQ: CVE-2026-11380
How does the CVE-2026-11380 stored-XSS attack work?
animation_effect to a value containing a double quote followed by an injected attribute such as onmouseover="alert(1)"; because the value is stored and rendered unescaped whenever the page loads, the injected script executes in the browser of any visitor who views that page.How severe is CVE-2026-11380?
Does exploiting CVE-2026-11380 require authentication?
How can I reproduce CVE-2026-11380?
animation_effect value and confirms the unescaped payload appears in the rendered page HTML.References for CVE-2026-11380
Authoritative sources for CVE-2026-11380 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.