You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
What Operating System are you using (both controller, and any agents involved in the problem)?
Jenkins and plugins versions report
Jenkins: 2.568.1
Relevant plugin: deploy-dashboard 0.1.0 (uses buildAddUrl step to add an Action with a raw getUrlName() URL containing multiple &-separated query parameters), but this is a Jenkins core bug, not plugin-specific.
What Operating System are you using (both controller, and any agents involved in the problem)?
Controller: official Jenkins Docker image (jenkins/jenkins) running on an Amazon Linux host (AWS EC2).
This is a client-side (browser) rendering bug — reproducible independent of controller/agent OS, since the double-escaping happens in browser-side JS (templates.js) when rendering the dropdown/jumplist menu.
Reproduction steps
Reproduction steps
Setup (clean Jenkins, matches our production version):
docker run -p 8080:8080 -p 50000:50000 jenkins/jenkins:2.568.1-lts
Complete the setup wizard, install suggested plugins.
Manage Jenkins → Plugins → Available plugins → install "Deploy Dashboard Plugin by Namecheap" (deploy-dashboard, https://plugins.jenkins.io/deploy-dashboard/) — this is the plugin providing the buildAddUrl step, but the bug reproduces with any Action whose getUrlName() returns a URL with multiple &-joined query parameters; buildAddUrl is just a convenient way to add one.
Create a Freestyle (or Pipeline) job named target-job with 3 String Parameters: a, b, c. Give it a single "Execute shell" step: echo "a=$a b=$b c=$c".
Create a second job named source-job (Pipeline) with this script:
Go to source-job's main page (or any dashboard view listing it) — in the build history list on the left, hover/click the chevron (⌄) next to build Translation to Brasilian Portuguese Br #1 to open the Actions dropdown ("jumplist").
Click "Deploy to target-job" in that dropdown.
Open target-job → last build → check the parameter values. Actual: only a=1 is set; b and c are empty/default (they arrive as amp;b/amp;c, which target-job doesn't recognize as parameters).
For direct proof without triggering a build: right-click the same dropdown link → Inspect, or in DevTools console run:
Click it → target-job triggers correctly with a=1&b=2&c=3 all set.
Inspect this sidebar link's href the same way — it correctly shows a literal &, not &.
Steps 9-10 vs 12-13 isolate the bug to the dropdown/jumplist JS renderer specifically; the server-rendered sidebar is unaffected.
Expected Results
When clicking an Action link (e.g. one added via buildAddUrl) from the build history dropdown/jumplist menu, all query parameters in the URL should be passed through to the target job unmodified — identical to clicking the same-titled link from the classic build sidebar.
Specifically, for a URL like /job/target-job/buildWithParameters?a=1&b=2&c=3, the resulting href in the DOM should contain a literal & between parameters (or its single, correctly-decoded HTML entity &), so that target-job receives all three parameters (a=1, b=2, c=3).
Actual Results
Only the first query parameter in the URL is passed to the target job. All subsequent parameters are lost — they arrive at the target job with a literal amp; prepended to their name (e.g. amp;b, amp;c instead of b, c), so the target job does not recognize them as its declared parameters and they fall back to empty/default values.
Inspecting the actual href attribute of the dropdown/jumplist link in DevTools confirms this: for a source URL of /job/target-job/buildWithParameters?a=1&b=2&c=3, the rendered href is:
This is literal text & sitting in the href value (not a decoded &), which is why the browser sends it verbatim in the query string on click, and the server then splits on the literal & inside &, treating amp;b and amp;c as (unknown) parameter names.
In our real-world case, this caused a deploy job's revision parameter to arrive empty, which in turn made a branch('${revision}') SCM step match an arbitrary ref ("Multiple candidate revisions") instead of the intended one — deploying unrelated/old code.
The classic sidebar-rendered link with the same URL works correctly and passes all parameters as expected.
Anything else?
This bug affects any plugin/feature that adds a build/job Action whose getUrlName() returns a URL with more than one query parameter, when that action is accessed through the newer JS-rendered dropdown/jumplist context menus (e.g. the chevron next to a build in the build history widget, or similar /contextMenu-backed overflow menus). It does not affect the classic server-rendered Jelly sidebar.
We noticed this while investigating a regression in the community "Deploy Dashboard" plugin (deploy-dashboard, https://plugins.jenkins.io/deploy-dashboard/) buildAddUrl step, but confirmed via code reading that the plugin itself does no client-side rendering at all (no custom Jelly for its Action) — the URL is rendered entirely by Jenkins core, so this is not a plugin bug.
We also found a related, but distinct, core bug fixed recently in a different overflow-menu component: "Fix breadcrumb overflow dropdown items failing to render" (#26978, merged for 2.571). That fix changed how the breadcrumbs overflow menu passes href/event data (switching from a bare url field to a structured event: {url, type} object), but it did not touch templates.js's menuItem()/optionalVal() double-escaping, which is the mechanism responsible for the bug reported here. We suspect the two are related symptoms of the same broader Bootstrap5/dropdown-menu UI migration (JENKINS-75727 and related), but this specific double-escaping issue appears to still be present as of 2.568.1 (verified in the jenkinsci/jenkins GitHub repo at tag jenkins-2.568.1).
Happy to submit a PR removing the redundant xmlEscape() call if a maintainer can confirm which of the two escaping sites should be removed (i.e. whether optionalVal should skip escaping for pre-escaped fields like href, or whether url construction in menuItem should pass the raw, unescaped string).
Are you interested in contributing a fix?
Yes — the fix looks like a small, low-risk one-line change in src/main/js/components/dropdowns/templates.js (menuItem()), removing one of the two redundant xmlEscape() calls. Happy to submit a PR.
The only thing we'd need guidance on is which of the two escaping sites is the "correct" one to keep, since removing the wrong one could reintroduce an XSS risk instead of just fixing the encoding bug:
Option A: keep the escape in optionalVal() (the generic, reusable helper used for all attributes), and change menuItem() to pass the rawitemOptions.event.url (plus context prefix) without pre-escaping it.
Option B: keep the escape in menuItem()'s url construction, and have optionalVal() accept a flag (or a pre-escaped marker) to skip re-escaping for that specific value.
We'd lean toward Option A as the more general fix (it keeps optionalVal() as the single source of truth for escaping any attribute value), but would appreciate a maintainer's confirmation before opening a PR, given this touches every dropdown item across the UI (build history, breadcrumbs, context menus, etc.) and we want to avoid a regression.
Jenkins and plugins versions report
What Operating System are you using (both controller, and any agents involved in the problem)?
Jenkins and plugins versions report
Jenkins: 2.568.1
Relevant plugin: deploy-dashboard 0.1.0 (uses
buildAddUrlstep to add anActionwith a rawgetUrlName()URL containing multiple&-separated query parameters), but this is a Jenkins core bug, not plugin-specific.What Operating System are you using (both controller, and any agents involved in the problem)?
Controller: official Jenkins Docker image (jenkins/jenkins) running on an Amazon Linux host (AWS EC2).
This is a client-side (browser) rendering bug — reproducible independent of controller/agent OS, since the double-escaping happens in browser-side JS (
templates.js) when rendering the dropdown/jumplist menu.Reproduction steps
Reproduction steps
Setup (clean Jenkins, matches our production version):
docker run -p 8080:8080 -p 50000:50000 jenkins/jenkins:2.568.1-ltsdeploy-dashboard, https://plugins.jenkins.io/deploy-dashboard/) — this is the plugin providing thebuildAddUrlstep, but the bug reproduces with any Action whosegetUrlName()returns a URL with multiple&-joined query parameters;buildAddUrlis just a convenient way to add one.target-jobwith 3 String Parameters:a,b,c. Give it a single "Execute shell" step:echo "a=$a b=$b c=$c".source-job(Pipeline) with this script:node { stage('Build') { buildAddUrl( title: 'Deploy to target-job', url: "/job/target-job/buildWithParameters?a=1&b=2&c=3" ) } }source-jobonce (build Translation to Brasilian Portuguese Br #1).Reproduce the bug (dropdown / jumplist):
source-job's main page (or any dashboard view listing it) — in the build history list on the left, hover/click the chevron (⌄) next to build Translation to Brasilian Portuguese Br #1 to open the Actions dropdown ("jumplist").target-job→ last build → check the parameter values. Actual: onlya=1is set;bandcare empty/default (they arrive asamp;b/amp;c, whichtarget-jobdoesn't recognize as parameters).Compare with the working case (classic sidebar):
source-job→#1). In the left sidebar, the same"Deploy to target-job"action link appears.target-jobtriggers correctly witha=1&b=2&c=3all set.hrefthe same way — it correctly shows a literal&, not&.Steps 9-10 vs 12-13 isolate the bug to the dropdown/jumplist JS renderer specifically; the server-rendered sidebar is unaffected.
Expected Results
When clicking an Action link (e.g. one added via
buildAddUrl) from the build history dropdown/jumplist menu, all query parameters in the URL should be passed through to the target job unmodified — identical to clicking the same-titled link from the classic build sidebar.Specifically, for a URL like
/job/target-job/buildWithParameters?a=1&b=2&c=3, the resultinghrefin the DOM should contain a literal&between parameters (or its single, correctly-decoded HTML entity&), so thattarget-jobreceives all three parameters (a=1,b=2,c=3).Actual Results
Only the first query parameter in the URL is passed to the target job. All subsequent parameters are lost — they arrive at the target job with a literal
amp;prepended to their name (e.g.amp;b,amp;cinstead ofb,c), so the target job does not recognize them as its declared parameters and they fall back to empty/default values.Inspecting the actual
hrefattribute of the dropdown/jumplist link in DevTools confirms this: for a source URL of/job/target-job/buildWithParameters?a=1&b=2&c=3, the renderedhrefis:/job/target-job/buildWithParameters?a=1&b=2&c=3This is literal text
&sitting in the href value (not a decoded&), which is why the browser sends it verbatim in the query string on click, and the server then splits on the literal&inside&, treatingamp;bandamp;cas (unknown) parameter names.In our real-world case, this caused a deploy job's
revisionparameter to arrive empty, which in turn made abranch('${revision}')SCM step match an arbitrary ref ("Multiple candidate revisions") instead of the intended one — deploying unrelated/old code.The classic sidebar-rendered link with the same URL works correctly and passes all parameters as expected.
Anything else?
This bug affects any plugin/feature that adds a build/job
ActionwhosegetUrlName()returns a URL with more than one query parameter, when that action is accessed through the newer JS-rendered dropdown/jumplist context menus (e.g. the chevron next to a build in the build history widget, or similar/contextMenu-backed overflow menus). It does not affect the classic server-rendered Jelly sidebar.We noticed this while investigating a regression in the community "Deploy Dashboard" plugin (
deploy-dashboard, https://plugins.jenkins.io/deploy-dashboard/)buildAddUrlstep, but confirmed via code reading that the plugin itself does no client-side rendering at all (no custom Jelly for itsAction) — the URL is rendered entirely by Jenkins core, so this is not a plugin bug.We also found a related, but distinct, core bug fixed recently in a different overflow-menu component: "Fix breadcrumb overflow dropdown items failing to render" (#26978, merged for 2.571). That fix changed how the breadcrumbs overflow menu passes
href/event data (switching from a bareurlfield to a structuredevent: {url, type}object), but it did not touchtemplates.js'smenuItem()/optionalVal()double-escaping, which is the mechanism responsible for the bug reported here. We suspect the two are related symptoms of the same broader Bootstrap5/dropdown-menu UI migration (JENKINS-75727 and related), but this specific double-escaping issue appears to still be present as of 2.568.1 (verified in thejenkinsci/jenkinsGitHub repo at tagjenkins-2.568.1).Happy to submit a PR removing the redundant
xmlEscape()call if a maintainer can confirm which of the two escaping sites should be removed (i.e. whetheroptionalValshould skip escaping for pre-escaped fields likehref, or whetherurlconstruction inmenuItemshould pass the raw, unescaped string).Are you interested in contributing a fix?
Yes — the fix looks like a small, low-risk one-line change in
src/main/js/components/dropdowns/templates.js(menuItem()), removing one of the two redundantxmlEscape()calls. Happy to submit a PR.The only thing we'd need guidance on is which of the two escaping sites is the "correct" one to keep, since removing the wrong one could reintroduce an XSS risk instead of just fixing the encoding bug:
optionalVal()(the generic, reusable helper used for all attributes), and changemenuItem()to pass the rawitemOptions.event.url(pluscontextprefix) without pre-escaping it.menuItem()'surlconstruction, and haveoptionalVal()accept a flag (or a pre-escaped marker) to skip re-escaping for that specific value.We'd lean toward Option A as the more general fix (it keeps
optionalVal()as the single source of truth for escaping any attribute value), but would appreciate a maintainer's confirmation before opening a PR, given this touches every dropdown item across the UI (build history, breadcrumbs, context menus, etc.) and we want to avoid a regression.