Skip to content

Fix double-escaping of URLs in dropdown menu items - #27221

Open
aroy114 wants to merge 1 commit into
jenkinsci:masterfrom
aroy114:fix/dropdown-double-escape
Open

Fix double-escaping of URLs in dropdown menu items#27221
aroy114 wants to merge 1 commit into
jenkinsci:masterfrom
aroy114:fix/dropdown-double-escape

Conversation

@aroy114

@aroy114 aroy114 commented Aug 10, 2026

Copy link
Copy Markdown

Fixes #27202

Root cause: templates.js applies xmlEscape() to Action URLs in three
places where it either double-escapes an already-correctly-escaped
value, or escapes a value headed to a non-HTML-parsing sink
(fetch() and form.setAttribute()), where entity escaping is never
undone and instead corrupts the literal string sent over the wire.

  1. menuItem() — the dropdown/jumplist href was escaped once when
    building url, then escaped again inside optionalVal(). This
    left literal & text in the rendered href instead of a real
    &, so only the first query parameter reached the server.
  2. tryPost() — the fetch() URL was escaped, but fetch() sends
    the string verbatim; escaping here corrupted POST action URLs the
    same way.
  3. tryConfirmationPost() — same issue via form.setAttribute("action", ...),
    which also sets a literal value, not HTML-parsed content.

All three now pass the raw URL through unescaped. optionalVal()
remains the single source of truth for HTML-attribute escaping and
still correctly escapes the href exactly once in menuItem().

Testing done

Reproduced the bug from #27202 locally and verified the fix for the
dropdown/jumplist href path (menuItem()):

  • Added an Action via buildAddUrl with a URL containing multiple
    &-joined query parameters (?a=1&b=2&c=3)
  • Before the fix: inspecting the dropdown/jumplist link's href in
    DevTools showed ...?a=1&b=2&c=3 (literal & text);
    clicking it only passed a=1 to the target job, b and c were
    dropped
  • After the fix: href renders as ...?a=1&b=2&c=3 (confirmed via
    the "This URL requires POST" interstitial, which echoes the exact
    URL being accessed with a real & in place), matching the working
    sidebar-rendered link
  • Triggered the build via "Retry using POST" and confirmed on the
    target job's Parameters page that all three parameters landed
    correctly: a=1, b=2, c=3

The tryPost() and tryConfirmationPost() fixes address the same
root-cause pattern (xmlEscape() applied to a URL/value passed to a
non-HTML-parsing sink — fetch() and form.setAttribute()
respectively, neither of which decode HTML entities). These were
identified by code inspection rather than independently exercised in
this testing pass; happy to add manual verification steps for these
if a reviewer can point me to an existing core Action that uses
either path.

Screenshots (UI changes only)

N/A — this is a behavioral fix (URL escaping), not a visual UI change.
See Testing done above for verification evidence.

Before

Dropdown/jumplist href contains literal & text; only the first
query parameter is passed through on click.

After

URL shown on the "This URL requires POST" interstitial contains a
real &; target job's Parameters page shows all three parameters
(a=1, b=2, c=3) populated correctly.

Proposed changelog entries

  • Fix dropdown and confirmation-post menu items dropping query parameters after the first & due to incorrect URL escaping

Proposed changelog category

/label bug

Proposed upgrade guidelines

N/A

Submitter checklist

  • The issue, if it exists, is well-described.
  • The changelog entries and upgrade guidelines are appropriate for the audience affected by the change and are in the imperative mood.
  • There is automated testing or an explanation as to why this change has no tests.
  • New public classes, fields, and methods are annotated with @Restricted or have @since TODO Javadocs, as appropriate.
    N/A — no new public classes/fields/methods.
  • New deprecations are annotated with @Deprecated(since = "TODO") or @Deprecated(forRemoval = true, since = "TODO"), if applicable.
    N/A — no deprecations.
  • UI changes do not introduce regressions when enforcing the current default rules of Content Security Policy Plugin. No new or substantially changed JavaScript is defined inline; no eval calls introduced.
  • For dependency updates, there are links to external changelogs and, if possible, full differentials.
    N/A — no dependency changes.
  • For new APIs and extension points, there is a link to at least one consumer.
    N/A — no new APIs.

Desired reviewers

@mention

Before the changes are marked as ready-for-merge:

Maintainer checklist

  • There are at least two (2) approvals for the pull request and no outstanding requests for change.
  • Conversations in the pull request are over, or it is explicit that a reviewer is not blocking the change.
  • Changelog entries in the pull request title and/or Proposed changelog entries are accurate, human-readable, and in the imperative mood.
  • Proper changelog labels are set so that the changelog can be generated automatically.
  • If the change needs additional upgrade steps from users, the upgrade-guide-needed label is set and there is a Proposed upgrade guidelines section in the pull request title (see example).
  • If it would make sense to backport the change to LTS, be a Bug or Improvement, and either the issue or pull request must be labeled as lts-candidate to be considered.

@welcome

welcome Bot commented Aug 10, 2026

Copy link
Copy Markdown

Yay, your first pull request towards Jenkins core was created successfully! Thank you so much!

A contributor will provide feedback soon. Meanwhile, you can join the chats and community forums to connect with other Jenkins users, developers, and maintainers.

@comment-ops-bot comment-ops-bot Bot added the bug For changelog: Minor bug. Will be listed after features label Aug 10, 2026
@MarkEWaite

Copy link
Copy Markdown
Contributor

@aroy114 thanks for the pull request.

Please don't request expedited review as a new contributor. Expedited review time is precious for Jenkins core maintainers and needs to be reserved for cases where the expedited review provides significant benefit to the community. When you @ mention core-pr-reviewers on something that should not have an expedited review, you distract maintainers from other work.

Please restore the maintainer checklist that you removed from the end of the pull request template. Maintainers use it as a reminder of the things they need to consider.

@aroy114

aroy114 commented Aug 10, 2026

Copy link
Copy Markdown
Author

Thanks for the feedback @MarkEWaite and apologies for both.
I've removed the reviewer mention and restored the maintainer checklist to the PR description.

@preetham-18-developer

Copy link
Copy Markdown

I think this should be opt.event.postTo rather than pt.event.postTo. I couldn't find pt defined in this scope, and ESLint reports it as undefined. Could you check this?

@preetham-18-developer

Copy link
Copy Markdown

Could we also add a test for tryConfirmationPost()? This path wasn't covered in the manual testing, and a test here could help catch issues like this.

@aroy114
aroy114 force-pushed the fix/dropdown-double-escape branch from c3f0001 to 1ed8b8e Compare August 12, 2026 07:55
@aroy114

aroy114 commented Aug 12, 2026

Copy link
Copy Markdown
Author

Thank you @preetham-18-developer, that was a typo (pt instead of opt), now fixed and pushed. Confirmed yarn lint is clean.

Happy to add a test for tryConfirmationPost(). Could you point me to an example of an existing frontend test in this module/area so I follow the right conventions?

@preetham-18-developer

Copy link
Copy Markdown

@aroy114 Sure. I'll check the existing tests around the dropdown components and share a relevant example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug For changelog: Minor bug. Will be listed after features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dropdown/jumplist menu items with multi-parameter query string hrefs get corrupted by double XML-escaping

3 participants