VYPR
Moderate severityNVD Advisory· Published Nov 12, 2021· Updated Aug 3, 2024

CVE-2021-21699

CVE-2021-21699

Description

Jenkins Active Choices Plugin 2.5.6 and earlier has a stored XSS vulnerability due to unescaped parameter names in reactive parameters, allowing attackers with Job/Configure permission to execute arbitrary JavaScript.

AI Insight

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

Jenkins Active Choices Plugin 2.5.6 and earlier has a stored XSS vulnerability due to unescaped parameter names in reactive parameters, allowing attackers with Job/Configure permission to execute arbitrary JavaScript.

Vulnerability

Jenkins Active Choices Plugin versions 2.5.6 and earlier do not escape the parameter name of reactive parameters and dynamic reference parameters, leading to a stored cross-site scripting (XSS) vulnerability [1][2][4]. This affects parameters in job configurations that are rendered as HTML UI widgets, where the parameter name is embedded directly into the page without proper sanitization [1]. The issue exists in the randomName attribute used for HTML element IDs in the plugin's tag library [3].

Exploitation

An attacker with Job/Configure permission can create or modify a freestyle job that uses reactive parameters or dynamic reference parameters, and set a malicious parameter name containing JavaScript code. When other users view or trigger the job, the unescaped parameter name is rendered in the HTML output, triggering the injected script in the victim's browser [1][2][4]. The attacker must have the ability to configure jobs (Job/Configure permission) but no further privileges are required [4].

Impact

Successful exploitation results in stored cross-site scripting (XSS) [2][4]. The attacker can execute arbitrary JavaScript in the context of the Jenkins web UI, potentially leading to session hijacking, credential theft, or performing administrative actions on behalf of the victim user [4]. The impact is constrained to the victim's browser session and the permissions of the victim within Jenkins.

Mitigation

Active Choices Plugin version 2.5.7, released on November 12, 2021, escapes references to parameter names using h.escape() [3][4]. Users should update to this version or later. No workaround is available for earlier versions without applying the patch. The plugin source code shows that the fix sanitizes the randomName attribute in multiple locations (e.g., table, div, tr elements) [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
org.biouno:uno-choiceMaven
< 2.5.72.5.7

Affected products

2

Patches

1
4529ab6413d4

[SECURITY-2219]

https://github.com/jenkinsci/active-choices-pluginBruno P. KinoshitaNov 11, 2021via ghsa
9 files changed · +204 40
  • src/main/resources/org/biouno/unochoice/CascadeChoiceParameter/index.jelly+4 4 modified
    @@ -21,14 +21,14 @@
             window.makeStaplerProxy = window.__old__makeStaplerProxy;
         }
         // find the cascade parameter element
    -    var parentDiv = jQuery('#${paramName}');
    +    var parentDiv = jQuery('#${h.escape(paramName)}');
         var parameterHtmlElement = parentDiv.find('DIV');
         if (!parameterHtmlElement || parameterHtmlElement.length == 0) {
             console.log('Could not find element by name, perhaps it is a DIV?');
             parameterHtmlElement = parentDiv.find('*[name="value"]');
         }
         if (parameterHtmlElement &amp;&amp; parameterHtmlElement.get(0)) {
    -        var cascadeParameter = new UnoChoice.CascadeParameter('${it.getName()}', parameterHtmlElement.get(0), '${it.getRandomName()}', cascadeChoiceParameter);
    +        var cascadeParameter = new UnoChoice.CascadeParameter('${h.escape(it.getName())}', parameterHtmlElement.get(0), '${h.escape(it.getRandomName())}', cascadeChoiceParameter);
             UnoChoice.cascadeParameters.push(cascadeParameter);
             // filter
             <j:if test="${it.filterable}">
    @@ -76,10 +76,10 @@
             }
     
             // call update methods in Java passing the HTML values
    -        console.log('Updating cascade of parameter [${it.getName()}] ...');
    +        console.log('Updating cascade of parameter [${h.escape(it.getName())}] ...');
             cascadeParameter.update();
         } else {
    -        console.log('Parameter error: Missing parameter [${paramName}] HTML element!');
    +        console.log('Parameter error: Missing parameter [${h.escape(paramName)}] HTML element!');
         }
     
       </script>
    
  • src/main/resources/org/biouno/unochoice/common/checkboxContent.jelly+8 8 modified
    @@ -6,12 +6,12 @@
           <d:tag name="randomTableIdBlock">
    
               <j:choose>
    
                   <j:when test="${divBasedFormLayout}">
    
    -                  <div id="tbl_ecp_${it.randomName}">
    
    +                  <div id="tbl_ecp_${h.escape(it.randomName)}">
    
                           <d:invokeBody/>
    
                       </div>
    
                   </j:when>
    
                   <j:otherwise>
    
    -                  <table id="tbl_ecp_${it.randomName}">
    
    +                  <table id="tbl_ecp_${h.escape(it.randomName)}">
    
                           <d:invokeBody/>
    
                       </table>
    
                   </j:otherwise>
    
    @@ -20,12 +20,12 @@
           <d:tag name="row">
    
               <j:choose>
    
                   <j:when test="${divBasedFormLayout}">
    
    -                  <div id="ecp_${it.randomName}_${index}" class="tr" style="white-space:nowrap">
    
    +                  <div id="ecp_${h.escape(it.randomName)}_${index}" class="tr" style="white-space:nowrap">
    
                           <d:invokeBody/>
    
                       </div>
    
                   </j:when>
    
                   <j:otherwise>
    
    -                  <tr id="ecp_${it.randomName}_${index}" style="white-space:nowrap">
    
    +                  <tr id="ecp_${h.escape(it.randomName)}_${index}" style="white-space:nowrap">
    
                           <d:invokeBody/>
    
                       </tr>
    
                   </j:otherwise>
    
    @@ -46,7 +46,7 @@
               </j:choose>
    
           </d:tag>
    
       </d:taglib>
    
    -  <div id="ecp_${it.randomName}" style="float:left; overflow-y:auto; padding-right:25px" class="dynamic_checkbox">
    
    +  <div id="ecp_${h.escape(it.randomName)}" style="float:left; overflow-y:auto; padding-right:25px" class="dynamic_checkbox">
    
         <j:set var="index" value="0"/>
    
         <transitionWrapper:randomTableIdBlock>
    
           <j:forEach var="iter" items="${it.getChoices()}" indexVar="indexVar">
    
    @@ -68,7 +68,7 @@
             <j:invokeStatic className="org.biouno.unochoice.util.Utils" method="escapeDisabled" var="escapedDisabledKey">
    
               <j:arg type="java.lang.Object" value="${iter.key}" />
    
             </j:invokeStatic>
    
    -        <j:set var="id" value="ecp_${it.randomName}_${index}" />
    
    +        <j:set var="id" value="ecp_${h.escape(it.randomName)}_${index}" />
    
             <transitionWrapper:row>
    
               <transitionWrapper:td>
    
                 <j:choose>
    
    @@ -108,7 +108,7 @@
               var height = 0;
    
               var maxCount = ${it.getVisibleItemCount()};
    
     
    
    -          var refElement = document.getElementById("ecp_${it.randomName}_0");
    
    +          var refElement = document.getElementById("ecp_${h.escape(it.randomName)}_0");
    
               if(maxCount > 0 && refElement && refElement.offsetHeight !=0) {
    
                   for(var i=0; i< maxCount; i++) {
    
                       height += refElement.offsetHeight + 3;
    
    @@ -119,7 +119,7 @@
               }
    
     
    
               height = Math.floor(height);
    
    -          document.getElementById("ecp_${it.randomName}").style.height = height + "px";
    
    +          document.getElementById("ecp_${h.escape(it.randomName)}").style.height = height + "px";
    
           };
    
     
    
           f();
    
    
  • src/main/resources/org/biouno/unochoice/common/choiceParameterCommon.jelly+2 2 modified
    @@ -4,7 +4,7 @@
       xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
       <st:adjunct includes="org.kohsuke.stapler.jquery"/>
       <st:adjunct includes="org.biouno.unochoice.stapler.unochoice"/>
    -  <j:set var="paramName" value="${it.randomName}" scope="parent" />
    +  <j:set var="paramName" value="${h.escape(it.randomName)}" scope="parent" />
       <j:set var="choiceType" value="${it.choiceType}"/>
       <j:set var="escapeEntryTitleAndDescription" value="false"/>
       <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">
    @@ -17,7 +17,7 @@
               </select>
             </j:when>
             <j:when test="${choiceType eq 'PT_MULTI_SELECT'}">
    -          <j:set var="multiple_select_id" value="multiple_select_${it.randomName}_${index}" />
    +          <j:set var="multiple_select_id" value="multiple_select_${h.escape(it.randomName)}_${index}" />
               <select name="value" multiple="multiple" size="1" id="${multiple_select_id}">
                 <st:include page="/org/biouno/unochoice/common/selectContent.jelly"/>
               </select>
    
  • src/main/resources/org/biouno/unochoice/common/radioContent.jelly+15 15 modified
    @@ -7,12 +7,12 @@
           <d:tag name="randomTableIdBlock">
    
               <j:choose>
    
                   <j:when test="${divBasedFormLayout}">
    
    -                  <div id="tbl_ecp_${it.randomName}">
    
    +                  <div id="tbl_ecp_${h.escape(it.randomName)}">
    
                           <d:invokeBody/>
    
                       </div>
    
                   </j:when>
    
                   <j:otherwise>
    
    -                  <table id="tbl_ecp_${it.randomName}">
    
    +                  <table id="tbl_ecp_${h.escape(it.randomName)}">
    
                           <d:invokeBody/>
    
                       </table>
    
                   </j:otherwise>
    
    @@ -21,12 +21,12 @@
           <d:tag name="row">
    
               <j:choose>
    
                   <j:when test="${divBasedFormLayout}">
    
    -                  <div id="tbl_tr_ecp_${it.randomName}" class="tr" style="white-space:nowrap">
    
    +                  <div id="tbl_tr_ecp_${h.escape(it.randomName)}" class="tr" style="white-space:nowrap">
    
                           <d:invokeBody/>
    
                       </div>
    
                   </j:when>
    
                   <j:otherwise>
    
    -                  <tr id="tbl_tr_ecp_${it.randomName}" style="white-space:nowrap">
    
    +                  <tr id="tbl_tr_ecp_${h.escape(it.randomName)}" style="white-space:nowrap">
    
                           <d:invokeBody/>
    
                       </tr>
    
                   </j:otherwise>
    
    @@ -47,7 +47,7 @@
               </j:choose>
    
           </d:tag>
    
       </d:taglib>
    
    -  <div id="ecp_${it.randomName}" style="float:left; overflow-y:auto; padding-right:25px">
    
    +  <div id="ecp_${h.escape(it.randomName)}" style="float:left; overflow-y:auto; padding-right:25px">
    
         <j:set var="index" value="0"/>
    
         <transitionWrapper:randomTableIdBlock>
    
           <j:forEach var="iter" items="${it.getChoices()}" indexVar="indexVar">
    
    @@ -69,7 +69,7 @@
             <j:invokeStatic className="org.biouno.unochoice.util.Utils" method="escapeDisabled" var="escapedDisabledKey">
    
               <j:arg type="java.lang.Object" value="${iter.key}" />
    
             </j:invokeStatic>
    
    -        <j:set var="id" value="ecp_${it.randomName}_${index}" />
    
    +        <j:set var="id" value="ecp_${h.escape(it.randomName)}_${index}" />
    
             <transitionWrapper:row>
    
               <transitionWrapper:td>
    
                 <j:choose>
    
    @@ -80,24 +80,24 @@
                     <j:invokeStatic className="org.biouno.unochoice.util.Utils" method="escapeDisabled" var="escapedAndDisabledKey">
    
                       <j:arg type="java.lang.Object" value="${escapedKey}" />
    
                     </j:invokeStatic>
    
    -                <input disabled="${disabled}" json="${escapedAndDisabledKey}" alt="${escapedAndDisabledValue}" otherid="${id}" checked="checked" name="${it.name}" value="${escapedAndDisabledKey}" class=" " type="radio" onchange="UnoChoice.fakeSelectRadioButton(&quot;${it.name}&quot;, &quot;${id}&quot;)" />
    
    +                <input disabled="${disabled}" json="${escapedAndDisabledKey}" alt="${escapedAndDisabledValue}" otherid="${id}" checked="checked" name="${h.escape(it.name)}" value="${escapedAndDisabledKey}" class=" " type="radio" onchange="UnoChoice.fakeSelectRadioButton(&quot;${h.escape(it.name)}&quot;, &quot;${id}&quot;)" />
    
                     <label class="attach-previous">${escapedAndDisabledValue}</label>
    
    -                <input disabled="${disabled}" json="${escapedAndDisabledKey}" name="value" value="${escapedAndDisabledKey}" class="${it.name}" type="hidden" id="${id}" title="${escapedAndDisabledValue}" />
    
    +                <input disabled="${disabled}" json="${escapedAndDisabledKey}" name="value" value="${escapedAndDisabledKey}" class="${h.escape(it.name)}" type="hidden" id="${id}" title="${escapedAndDisabledValue}" />
    
                   </j:when>
    
                   <j:when test="${selected}">
    
    -                <input json="${escapedKey}" alt="${escapedValue}" otherid="${id}" checked="checked" name="${it.name}" value="${escapedKey}" class=" " type="radio" onchange="UnoChoice.fakeSelectRadioButton(&quot;${it.name}&quot;, &quot;${id}&quot;)" />
    
    +                <input json="${escapedKey}" alt="${escapedValue}" otherid="${id}" checked="checked" name="${h.escape(it.name)}" value="${escapedKey}" class=" " type="radio" onchange="UnoChoice.fakeSelectRadioButton(&quot;${h.escape(it.name)}&quot;, &quot;${id}&quot;)" />
    
                     <label class="attach-previous">${escapedValue}</label>
    
    -                <input json="${escapedKey}" name="value" value="${escapedKey}" class="${it.name}" type="hidden" id="${id}" title="${escapedValue}" />
    
    +                <input json="${escapedKey}" name="value" value="${escapedKey}" class="${h.escape(it.name)}" type="hidden" id="${id}" title="${escapedValue}" />
    
                   </j:when>
    
                   <j:when test="${disabled}">
    
    -                <input disabled="${disabled}" json="${escapedDisabledKey}" alt="${escapedDisabledValue}" otherid="${id}" name="${it.name}" value="${escapedDisabledKey}" class=" " type="radio" onchange="UnoChoice.fakeSelectRadioButton(&quot;${it.name}&quot;, &quot;${id}&quot;)" />
    
    +                <input disabled="${disabled}" json="${escapedDisabledKey}" alt="${escapedDisabledValue}" otherid="${id}" name="${h.escape(it.name)}" value="${escapedDisabledKey}" class=" " type="radio" onchange="UnoChoice.fakeSelectRadioButton(&quot;${h.escape(it.name)}&quot;, &quot;${id}&quot;)" />
    
                     <label class="attach-previous">${escapedDisabledValue}</label>
    
    -                <input disabled="${disabled}" json="${escapedDisabledKey}" name="" value="${escapedDisabledKey}" class="${it.name}" type="hidden" id="${id}" title="${escapedDisabledValue}" />
    
    +                <input disabled="${disabled}" json="${escapedDisabledKey}" name="" value="${escapedDisabledKey}" class="${h.escape(it.name)}" type="hidden" id="${id}" title="${escapedDisabledValue}" />
    
                   </j:when>
    
                   <j:otherwise>
    
    -                <input json="${escapedKey}" alt="${escapedValue}" otherid="${id}" name="${it.name}" value="${escapedKey}" class=" " type="radio" onchange="UnoChoice.fakeSelectRadioButton(&quot;${it.name}&quot;, &quot;${id}&quot;)" />
    
    +                <input json="${escapedKey}" alt="${escapedValue}" otherid="${id}" name="${h.escape(it.name)}" value="${escapedKey}" class=" " type="radio" onchange="UnoChoice.fakeSelectRadioButton(&quot;${h.escape(it.name)}&quot;, &quot;${id}&quot;)" />
    
                     <label class="attach-previous">${escapedValue}</label>
    
    -                <input json="${escapedKey}" name="" value="${escapedKey}" class="${it.name}" type="hidden" id="${id}" title="${escapedValue}" />
    
    +                <input json="${escapedKey}" name="" value="${escapedKey}" class="${h.escape(it.name)}" type="hidden" id="${id}" title="${escapedValue}" />
    
                   </j:otherwise>
    
                 </j:choose>
    
               </transitionWrapper:td>
    
    @@ -127,7 +127,7 @@
               }
    
     
    
               height = Math.floor(height);
    
    -          document.getElementById("ecp_${it.randomName}").style.height = height + "px";
    
    +          document.getElementById("ecp_${h.escape(it.randomName)}").style.height = height + "px";
    
           };
    
     
    
           f();
    
    
  • src/main/resources/org/biouno/unochoice/DynamicReferenceParameter/index.jelly+3 3 modified
    @@ -105,7 +105,7 @@
             }
         }
         if (parameterHtmlElement &amp;&amp; parameterHtmlElement.get(0)) {
    -        var dynamicParameter = new UnoChoice.DynamicReferenceParameter('${it.getName()}', parameterHtmlElement.get(0), dynamicReferenceParameter);
    +        var dynamicParameter = new UnoChoice.DynamicReferenceParameter('${h.escape(it.getName())}', parameterHtmlElement.get(0), dynamicReferenceParameter);
             UnoChoice.cascadeParameters.push(dynamicParameter); // TODO review whether it is right or not to add a dynamic parameter here
             for (var i  = 0; i &lt; referencedParameters.length ; ++i) {
                 var parameterElement = null;
    @@ -142,10 +142,10 @@
             }
     
             // call update methods in Java passing the HTML values
    -        console.log('Updating cascade of parameter [${it.getName()}] ...');
    +        console.log('Updating cascade of parameter [${h.escape(it.getName())}] ...');
             dynamicParameter.update();
         } else {
    -        console.log('Parameter error: Missing parameter [${paramName}] HTML element!');
    +        console.log('Parameter error: Missing parameter [${h.escape(paramName)}] HTML element!');
         }
       </script>
     </j:jelly>
    
  • src/main/resources/org/biouno/unochoice/DynamicReferenceParameter/inputElement.jelly+1 1 modified
    @@ -2,5 +2,5 @@
     <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
       xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
       xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
    -  <input id='inputElement_${paramName}' type="text" value="${it.getChoicesAsString()}" readonly="readonly" disabled="disabled" />
    +  <input id='inputElement_${h.escape(paramName)}' type="text" value="${it.getChoicesAsString()}" readonly="readonly" disabled="disabled" />
     </j:jelly>
    \ No newline at end of file
    
  • src/test/java/org/biouno/unochoice/jenkins_cert_1954/TestParametersXssVulnerabilities.java+1 1 modified
    @@ -1,7 +1,7 @@
     /*
      * The MIT License (MIT)
      *
    - * Copyright (c) 2014-2020 Ioannis Moutsatsos, Bruno P. Kinoshita
    + * Copyright (c) 2014-2021 Ioannis Moutsatsos, Bruno P. Kinoshita
      *
      * Permission is hereby granted, free of charge, to any person obtaining a copy
      * of this software and associated documentation files (the "Software"), to deal
    
  • src/test/java/org/biouno/unochoice/jenkins_cert_2192/TestDynamicReferenceXss.java+6 6 modified
    @@ -1,7 +1,7 @@
     /*
      * The MIT License (MIT)
      *
    - * Copyright (c) 2014-2020 Ioannis Moutsatsos, Bruno P. Kinoshita
    + * Copyright (c) 2014-2021 Ioannis Moutsatsos, Bruno P. Kinoshita
      *
      * Permission is hereby granted, free of charge, to any person obtaining a copy
      * of this software and associated documentation files (the "Software"), to deal
    @@ -52,7 +52,7 @@
      * XSS case.
      *
      * @since 2.5.2
    - * @see {@link hudson.Util#escape}
    + * @see hudson.Util#escape(String)
      */
     @Issue("2192")
     public class TestDynamicReferenceXss {
    @@ -68,7 +68,7 @@ public class TestDynamicReferenceXss {
         @Test
         public void testChoicesParameterXss() throws IOException, SAXException {
             FreeStyleProject project = j.createFreeStyleProject();
    -        String scriptText = String.format("return ['OK']");
    +        String scriptText = "return ['OK']";
             SecureGroovyScript secureScript = new SecureGroovyScript(scriptText, true, null);
             GroovyScript script = new GroovyScript(secureScript, secureScript);
     
    @@ -91,17 +91,17 @@ public void testChoicesParameterXss() throws IOException, SAXException {
             wc.setAlertHandler(alertHandler);
     
             wc.goTo("job/" + project.getName() + "/build?delay=0sec");
    -        final List<String> alertmsgs = alertHandler.getCollectedAlerts();
    +        final List<String> alerts = alertHandler.getCollectedAlerts();
     
    -        assertEquals("You got a JS alert, look out for XSS!", 0, alertmsgs.size());
    +        assertEquals("You got a JS alert, look out for XSS!", 0, alerts.size());
         }
     
         /**
          * Test that the reference parameter value is escaped.
          */
         @Test
         public void testGetReferencedParametersAsArray() {
    -        String scriptText = String.format("return ['OK']");
    +        String scriptText = "return ['OK']";
             SecureGroovyScript secureScript = new SecureGroovyScript(scriptText, true, null);
             GroovyScript script = new GroovyScript(secureScript, secureScript);
             final String xssString = "\"+alert(123)+\"";
    
  • src/test/java/org/biouno/unochoice/jenkins_cert_2219/TestXssParameterNameBrowserConsole.java+164 0 added
    @@ -0,0 +1,164 @@
    +/*
    + * The MIT License (MIT)
    + *
    + * Copyright (c) 2014-2021 Ioannis Moutsatsos, Bruno P. Kinoshita
    + *
    + * Permission is hereby granted, free of charge, to any person obtaining a copy
    + * of this software and associated documentation files (the "Software"), to deal
    + * in the Software without restriction, including without limitation the rights
    + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    + * copies of the Software, and to permit persons to whom the Software is
    + * furnished to do so, subject to the following conditions:
    + *
    + * The above copyright notice and this permission notice shall be included in
    + * all copies or substantial portions of the Software.
    + *
    + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    + * THE SOFTWARE.
    + */
    +package org.biouno.unochoice.jenkins_cert_2219;
    +
    +import com.gargoylesoftware.htmlunit.CollectingAlertHandler;
    +import hudson.model.FreeStyleProject;
    +import hudson.model.ParametersDefinitionProperty;
    +import org.biouno.unochoice.CascadeChoiceParameter;
    +import org.biouno.unochoice.ChoiceParameter;
    +import org.biouno.unochoice.DynamicReferenceParameter;
    +import org.biouno.unochoice.model.GroovyScript;
    +import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript;
    +import org.junit.Rule;
    +import org.junit.Test;
    +import org.jvnet.hudson.test.Issue;
    +import org.jvnet.hudson.test.JenkinsRule;
    +import org.jvnet.hudson.test.JenkinsRule.WebClient;
    +import org.xml.sax.SAXException;
    +
    +import java.io.IOException;
    +import java.util.List;
    +
    +import static org.junit.Assert.assertEquals;
    +
    +/**
    + * Prevent a case where a parameter name is logged to the browser console,
    + * causing an XSS security issue.
    + *
    + * <p>Tests the three parameters, {@code ChoiceParameter}, {@code CascadeChoiceParameter},
    + * and {@code DynamicReferenceParameter}.</p>
    + *
    + * @since 2.6.0
    + * @see hudson.Util#escape(String)
    + */
    +@Issue("2219")
    +public class TestXssParameterNameBrowserConsole {
    +
    +    @Rule
    +    public JenkinsRule j = new JenkinsRule();
    +
    +    /**
    +     * Tests that a {@code ChoiceParameter} reference parameter name is sanitized against XSS.
    +     * @throws IOException if it fails to load the script
    +     * @throws SAXException if the XML is malformed
    +     */
    +    @Test
    +    public void testChoiceParameterXss() throws IOException, SAXException {
    +        final FreeStyleProject project = j.createFreeStyleProject();
    +        final String scriptText = "return ['1']";
    +        final SecureGroovyScript secureScript = new SecureGroovyScript(scriptText, true, null);
    +        final GroovyScript script = new GroovyScript(secureScript, secureScript);
    +
    +        ChoiceParameter parameter = new ChoiceParameter(
    +                "]');alert(\"XSS\");console.log('[",
    +                "]');alert(\"XSS\");console.log('[",
    +                "random-name", // using ]');alert("XSS");console.log('[ as random-name breaks the JS code, with no XSS
    +                script,
    +                ChoiceParameter.PARAMETER_TYPE_RADIO,
    +                false,
    +                1);
    +        project.addProperty(new ParametersDefinitionProperty(parameter));
    +        project.save();
    +
    +        final CollectingAlertHandler alertHandler = new CollectingAlertHandler();
    +        final WebClient wc = j.createWebClient();
    +        wc.setThrowExceptionOnFailingStatusCode(false);
    +        wc.setAlertHandler(alertHandler);
    +
    +        wc.goTo("job/" + project.getName() + "/build?delay=0sec");
    +        final List<String> alerts = alertHandler.getCollectedAlerts();
    +
    +        assertEquals("You got a JS alert, look out for XSS!", 0, alerts.size());
    +    }
    +
    +    /**
    +     * Tests that a {@code CascadeChoiceParameter} reference parameter name is sanitized against XSS.
    +     * @throws IOException if it fails to load the script
    +     * @throws SAXException if the XML is malformed
    +     */
    +    @Test
    +    public void testCascadeChoiceParameterXss() throws IOException, SAXException {
    +        final FreeStyleProject project = j.createFreeStyleProject();
    +        final String scriptText = "return ['1']";
    +        final SecureGroovyScript secureScript = new SecureGroovyScript(scriptText, true, null);
    +        final GroovyScript script = new GroovyScript(secureScript, secureScript);
    +
    +        CascadeChoiceParameter parameter = new CascadeChoiceParameter(
    +                "]');alert(\"XSS\");console.log('[",
    +                "]');alert(\"XSS\");console.log('[",
    +                "random-name", // using ]');alert("XSS");console.log('[ as random-name breaks the JS code, with no XSS
    +                script,
    +                ChoiceParameter.PARAMETER_TYPE_RADIO,
    +                "]');alert(\"XSS\");console.log('[",
    +                false,
    +                1);
    +        project.addProperty(new ParametersDefinitionProperty(parameter));
    +        project.save();
    +
    +        final CollectingAlertHandler alertHandler = new CollectingAlertHandler();
    +        final WebClient wc = j.createWebClient();
    +        wc.setThrowExceptionOnFailingStatusCode(false);
    +        wc.setAlertHandler(alertHandler);
    +
    +        wc.goTo("job/" + project.getName() + "/build?delay=0sec");
    +        final List<String> alerts = alertHandler.getCollectedAlerts();
    +
    +        assertEquals("You got a JS alert, look out for XSS!", 0, alerts.size());
    +    }
    +
    +    /**
    +     * Tests that a {@code DynamicReferenceParameter} reference parameter name is sanitized against XSS.
    +     * @throws IOException if it fails to load the script
    +     * @throws SAXException if the XML is malformed
    +     */
    +    @Test
    +    public void testDynamicReferenceParameterXss() throws IOException, SAXException {
    +        final FreeStyleProject project = j.createFreeStyleProject();
    +        final String scriptText = "return ['1']";
    +        final SecureGroovyScript secureScript = new SecureGroovyScript(scriptText, true, null);
    +        final GroovyScript script = new GroovyScript(secureScript, secureScript);
    +
    +        DynamicReferenceParameter parameter = new DynamicReferenceParameter(
    +                "]');alert(\"XSS\");console.log('[",
    +                "]');alert(\"XSS\");console.log('[",
    +                "random-name", // using ]');alert("XSS");console.log('[ as random-name breaks the JS code, with no XSS
    +                script,
    +                ChoiceParameter.ELEMENT_TYPE_ORDERED_LIST,
    +                "]');alert(\"XSS\");console.log('[",
    +                false);
    +        project.addProperty(new ParametersDefinitionProperty(parameter));
    +        project.save();
    +
    +        final CollectingAlertHandler alertHandler = new CollectingAlertHandler();
    +        final WebClient wc = j.createWebClient();
    +        wc.setThrowExceptionOnFailingStatusCode(false);
    +        wc.setAlertHandler(alertHandler);
    +
    +        wc.goTo("job/" + project.getName() + "/build?delay=0sec");
    +        final List<String> alerts = alertHandler.getCollectedAlerts();
    +
    +        assertEquals("You got a JS alert, look out for XSS!", 0, alerts.size());
    +    }
    +}
    

Vulnerability mechanics

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

References

5

News mentions

1