VYPR
Moderate severityNVD Advisory· Published Jul 20, 2022· Updated Apr 22, 2025

jQuery UI contains potential XSS vulnerability when refreshing a checkboxradio with an HTML-like initial text label

CVE-2022-31160

Description

jQuery UI is a curated set of user interface interactions, effects, widgets, and themes built on top of jQuery. Versions prior to 1.13.2 are potentially vulnerable to cross-site scripting. Initializing a checkboxradio widget on an input enclosed within a label makes that parent label contents considered as the input label. Calling .checkboxradio( "refresh" ) on such a widget and the initial HTML contained encoded HTML entities will make them erroneously get decoded. This can lead to potentially executing JavaScript code. The bug has been patched in jQuery UI 1.13.2. To remediate the issue, someone who can change the initial HTML can wrap all the non-input contents of the label in a span.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Cross-site scripting (XSS) vulnerability in jQuery UI's checkboxradio widget prior to 1.13.2 due to improper handling of HTML entities in label contents.

Vulnerability

Overview

CVE-2022-31160 is a cross-site scripting (XSS) vulnerability in the jQuery UI library, specifically in the checkboxradio widget. Versions prior to 1.13.2 are affected. The root cause is that when a checkboxradio widget is initialized on an ` element enclosed within a , the widget treats the entire contents of the parent as the input's label. When .checkboxradio("refresh")` is called, the widget erroneously decodes HTML entities present in the initial label text. This can lead to the execution of arbitrary JavaScript if the initial HTML contains encoded malicious script tags [1][2].

Exploitation

The attack requires that an attacker can influence the initial HTML content of a ` element that wraps an used with a checkboxradio widget. The vulnerable code path is triggered when .checkboxradio("refresh") is invoked. No special network position or authentication is required beyond the ability to inject or modify the page's HTML (e.g., via stored XSS in a comment or CMS). The fix commit adds test cases illustrating the issue: an initial label containing <em>Hi, I'm a label</em> would be incorrectly decoded to Hi, I'm a label`, potentially allowing script injection if further manipulated [2].

Impact

An attacker who successfully exploits this vulnerability can execute arbitrary JavaScript in the context of the victim's browser session. This could lead to session hijacking, data theft, or defacement. The vulnerability is classified as cross-site scripting (XSS) and has a CVSS score of 6.1 (Medium) [1].

Mitigation

The bug has been patched in jQuery UI version 1.13.2, released on July 20, 2022 [3]. Users should update to this version or later. As a workaround, if updating is not immediately possible, the advisory notes that wrapping all non-input contents of the ` in a ` can prevent the decoding behavior. The jQuery UI project is in maintenance mode, but security fixes are still being provided [3].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
jquery-uinpm
< 1.13.21.13.2
org.webjars.npm:jquery-uiMaven
< 1.13.21.13.2
jquery-ui-railsRubyGems
< 8.0.08.0.0
jQuery.UI.CombinedNuGet
< 1.13.21.13.2

Affected products

5

Patches

1
8cc5bae1caa1

Checkboxradio: Don't re-evaluate text labels as HTML

https://github.com/jquery/jquery-uiMichał Gołębiowski-OwczarekJul 14, 2022via ghsa
4 files changed · +100 8
  • tests/unit/checkboxradio/checkboxradio.html+12 0 modified
    @@ -64,6 +64,18 @@
     <label>
     	<input type="checkbox" id="label-with-no-for"/>
     </label>
    +<label>
    +	<input type="checkbox" id="label-with-no-for-with-html"/>
    +	<strong>Hi</strong>, <em>I'm a label</em>
    +</label>
    +<label>
    +	<input type="checkbox" id="label-with-no-for-with-text"/>
    +	Hi, I'm a label
    +</label>
    +<label>
    +	<input type="checkbox" id="label-with-no-for-with-html-like-text"/>
    +	&lt;em&gt;Hi, I'm a label&lt;/em&gt;
    +</label>
     
     <form id="form3"></form>
     <input type="radio" name="crazy-form" id="crazy-form-1" form="form3" checked="checked">
    
  • tests/unit/checkboxradio/core.js+37 0 modified
    @@ -131,4 +131,41 @@ QUnit.test( "Calling checkboxradio on an input with no label throws an error", f
     	);
     } );
     
    +QUnit.test( "Inheriting label from initial HTML", function( assert ) {
    +	var tests = [
    +		{
    +			id: "label-with-no-for-with-html",
    +			expectedLabel: "<strong>Hi</strong>, <em>I'm a label</em>"
    +		},
    +		{
    +			id: "label-with-no-for-with-text",
    +			expectedLabel: "Hi, I'm a label"
    +		},
    +		{
    +			id: "label-with-no-for-with-html-like-text",
    +			expectedLabel: "&lt;em&gt;Hi, I'm a label&lt;/em&gt;"
    +		}
    +	];
    +
    +	assert.expect( tests.length );
    +
    +	tests.forEach( function( testData ) {
    +		var id = testData.id;
    +		var expectedLabel = testData.expectedLabel;
    +		var inputElem = $( "#" + id );
    +		var labelElem = inputElem.parent();
    +
    +		inputElem.checkboxradio( { icon: false } );
    +
    +		var labelWithoutInput = labelElem.clone();
    +		labelWithoutInput.find( "input" ).remove();
    +
    +		assert.strictEqual(
    +			labelWithoutInput.html().trim(),
    +			expectedLabel.trim(),
    +			"Label correct [" + id + "]"
    +		);
    +	} );
    +} );
    +
     } );
    
  • tests/unit/checkboxradio/methods.js+38 0 modified
    @@ -96,4 +96,42 @@ QUnit.test( "Input wrapped in a label preserved on refresh", function( assert )
     	assert.strictEqual( input.parent()[ 0 ], element[ 0 ], "Input preserved" );
     } );
     
    +QUnit.test( "Initial text label not turned to HTML on refresh", function( assert ) {
    +	var tests = [
    +		{
    +			id: "label-with-no-for-with-html",
    +			expectedLabel: "<strong>Hi</strong>, <em>I'm a label</em>"
    +		},
    +		{
    +			id: "label-with-no-for-with-text",
    +			expectedLabel: "Hi, I'm a label"
    +		},
    +		{
    +			id: "label-with-no-for-with-html-like-text",
    +			expectedLabel: "&lt;em&gt;Hi, I'm a label&lt;/em&gt;"
    +		}
    +	];
    +
    +	assert.expect( tests.length );
    +
    +	tests.forEach( function( testData ) {
    +		var id = testData.id;
    +		var expectedLabel = testData.expectedLabel;
    +		var inputElem = $( "#" + id );
    +		var labelElem = inputElem.parent();
    +
    +		inputElem.checkboxradio( { icon: false } );
    +		inputElem.checkboxradio( "refresh" );
    +
    +		var labelWithoutInput = labelElem.clone();
    +		labelWithoutInput.find( "input" ).remove();
    +
    +		assert.strictEqual(
    +			labelWithoutInput.html().trim(),
    +			expectedLabel.trim(),
    +			"Label correct [" + id + "]"
    +		);
    +	} );
    +} );
    +
     } );
    
  • ui/widgets/checkboxradio.js+13 8 modified
    @@ -50,8 +50,7 @@ $.widget( "ui.checkboxradio", [ $.ui.formResetMixin, {
     	},
     
     	_getCreateOptions: function() {
    -		var disabled, labels;
    -		var that = this;
    +		var disabled, labels, labelContents;
     		var options = this._super() || {};
     
     		// We read the type here, because it makes more sense to throw a element type error first,
    @@ -71,12 +70,18 @@ $.widget( "ui.checkboxradio", [ $.ui.formResetMixin, {
     
     		// We need to get the label text but this may also need to make sure it does not contain the
     		// input itself.
    -		this.label.contents().not( this.element[ 0 ] ).each( function() {
    -
    -			// The label contents could be text, html, or a mix. We concat each element to get a
    -			// string representation of the label, without the input as part of it.
    -			that.originalLabel += this.nodeType === 3 ? $( this ).text() : this.outerHTML;
    -		} );
    +		// The label contents could be text, html, or a mix. We wrap all elements
    +		// and read the wrapper's `innerHTML` to get a string representation of
    +		// the label, without the input as part of it.
    +		labelContents = this.label.contents().not( this.element[ 0 ] );
    +
    +		if ( labelContents.length ) {
    +			this.originalLabel += labelContents
    +				.clone()
    +				.wrapAll( "<div></div>" )
    +				.parent()
    +				.html();
    +		}
     
     		// Set the label option if we found label text
     		if ( this.originalLabel ) {
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

19

News mentions

0

No linked articles in our index yet.