# Open WebUI has Stored XSS in Banner Component via Improper Sanitization Order

Open WebUI banner sanitized markdown with DOMPurify before marked.parse, letting a malicious admin plant a javascript: link that steals the Super Admin's session token.

- Payout: Research
- Severity: high
- Program: Open WebUI
- Found by: POV9en
- Disclosed: 2026-05-14
- CVE: CVE-2026-45665
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-cqp4-qqvg-3787
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-cqp4-qqvg-3787
- Tags: area:web domain:web impact:ato impact:privesc kind:research platform:ghsa sev:high tech:nodejs technique:source-review vuln:privilege-escalation vuln:xss

**Target:** Svelte Banner component rendering markdown via {@html}
**Root cause:** DOMPurify.sanitize() runs on raw markdown before marked.parse() converts it to HTML, so markdown link syntax with a javascript: URL passes sanitization and becomes an unsafe anchor rendered via {@html}.
**Steps:**
1. Log in as a compromised admin and go to Settings > Interface > UI > Banners.
2. Add a banner with content `[Click for Security Update](javascript:alert(localStorage.token))` and Save.
3. Primary Admin loads the dashboard, sees the banner, and clicks the link.
4. JavaScript executes in the Super Admin session, exposing localStorage.token.
**How found:** Reporter noticed the sanitization order in Banner.svelte: DOMPurify runs before marked.parse, so markdown-only syntax (not HTML) bypasses the filter and is later converted into an executable link.
**Exploitation:** A compromised or malicious admin stores a markdown banner whose link target is a javascript: URI. Because sanitization precedes markdown parsing, the payload survives and is rendered as an anchor for every user. The Primary Admin clicks the trusted-looking banner and the script runs in their active session, yielding their full-access token and bypassing MFA since the session is already authenticated.
**Escalation:** Low-privilege admin plants the banner to capture the Primary Admin's session token, escalating to full system control.
**Insight:** When markdown is rendered to HTML, sanitize the parser output, not the input; markdown link syntax like `[x](javascript:...)` is inert text to an HTML sanitizer but becomes a live href after parsing.

## Proof of concept (copied exactly from the original report)

**Markdown javascript: link XSS payload**

```
[Click for Security Update](javascript:alert(localStorage.token))
```

**Vulnerable sanitization order in Banner.svelte**

```
{@html marked.parse(DOMPurify.sanitize((banner?.content ?? '').replace(/\n/g, '<br>')))}
```

