Severity by source
AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N
Admin write access to overlay config requires PR:H; pending user must click link (UI:R); XSS crosses user boundary yielding S:C with partial C/I and no availability impact.
Primary rating from GitHub Advisory.
CVSS VectorGitHub Advisory
Lifecycle Timeline
3DescriptionGitHub Advisory
Vulnerability Details
CWE-79: Cross-site Scripting (XSS)
The AccountPending.svelte component renders the admin-configured "Pending User Overlay Content" using marked.parse() inside {@html} with an incorrect DOMPurify application order:
Vulnerable Code
src/lib/components/layout/Overlay/AccountPending.svelte (lines 43-48):
{@html marked.parse(
DOMPurify.sanitize(
($config?.ui?.pending_user_overlay_content ?? '').replace(/\n/g, '<br>')
)
)}DOMPurify is applied to the raw Markdown input before marked.parse() processes it. This is the wrong order. DOMPurify sanitizes the Markdown text (which contains no HTML tags), then marked.parse() converts Markdown link syntax into HTML <a> tags with javascript: href, and the result is rendered with {@html} unsanitized.
The correct pattern (used elsewhere in the codebase, e.g., NotebookView.svelte:77) is:
DOMPurify.sanitize(marked.parse(src)) // sanitize AFTER markdown parsingSteps to Reproduce
Prerequisites
- Open WebUI v0.8.10
- Admin account
- A second user account with "pending" role
Steps
- Log in as admin and navigate to Admin Settings → Settings → General.
- Set Default User Role to
pending. - In the Pending User Overlay Content field, enter:
# Account Pending
Your account is under review.
[Contact Support](javascript:alert(document.domain))- Save the settings.
- In a separate browser (or incognito window), create a new account or log in as a pending user.
- The pending overlay is displayed. Click the "Contact Support" link.
- A JavaScript alert dialog appears showing
localhost(the document domain), confirming XSS execution.
Verified Output
The alert(document.domain) executes successfully, displaying "localhost" in a JavaScript dialog box.
Impact
An admin can inject arbitrary JavaScript into the Pending User Overlay Content that executes in the browser context of any pending user who views the overlay page. This could be used to:
- Session hijacking: Steal pending users' JWT tokens from cookies/localStorage
- Credential theft: Replace the pending overlay with a fake login form
- Phishing: Redirect pending users to malicious sites
While this requires admin privileges to set the overlay content, it enables an admin to attack pending users (who have not yet been granted full access). In multi-admin deployments, a compromised admin account could use this to escalate attacks.
Proposed Fix
Apply DOMPurify after marked.parse(), not before:
<!-- Before (vulnerable): -->
{@html marked.parse(
DOMPurify.sanitize(
($config?.ui?.pending_user_overlay_content ?? '').replace(/\n/g, '<br>')
)
)}
<!-- After (fixed): -->
{@html DOMPurify.sanitize(
marked.parse(
($config?.ui?.pending_user_overlay_content ?? '').replace(/\n/g, '<br>'),
{ async: false }
)
)}<img width="1510" height="1093" alt="2026-03-23_03-07" src="https://github.com/user-attachments/assets/bcc94dd6-4f06-472b-9979-9759458c76b3" />
AnalysisAI
Stored XSS in Open WebUI's AccountPending.svelte allows an authenticated admin to inject arbitrary JavaScript into the pending user overlay content, which executes in the browser session of any user holding the 'pending' role. The flaw stems from invoking DOMPurify before rather than after marked.parse(), permitting Markdown link syntax with javascript: URIs to survive sanitization and render as executable HTML via Svelte's {@html} directive. No CISA KEV listing exists, but a proof-of-concept is confirmed by SSVC data; EPSS is low at 0.03% (8th percentile), consistent with the high-privilege prerequisite limiting mass exploitation.
Technical ContextAI
Open WebUI is a Svelte-based frontend (pip package open-webui, CPE: pkg:pip/open-webui) for managing local AI models. The vulnerable component is src/lib/components/layout/Overlay/AccountPending.svelte (lines 43-48), which renders admin-configured Markdown overlay content using a two-step pipeline: DOMPurify.sanitize() followed by marked.parse(), with the output injected via Svelte's raw {@html} directive. The critical error is ordering: DOMPurify receives the raw Markdown string before it has been converted to HTML. Because the Markdown source contains no HTML tags, DOMPurify passes it through unchanged. marked.parse() then converts Markdown link syntax - e.g. [text](javascript:alert()) - into <a href='javascript:...'> anchor elements, which are rendered unsanitized into the DOM. The correct pattern, applied in NotebookView.svelte:77 elsewhere in the same codebase, wraps DOMPurify.sanitize() around the output of marked.parse(). CWE-79 (Improper Neutralization of Input During Web Page Generation) precisely describes this sanitization bypass - a defense-in-depth control exists but is applied at the wrong pipeline stage, rendering it ineffective.
RemediationAI
Upgrade to Open WebUI 0.9.0 or later, which corrects the DOMPurify ordering in AccountPending.svelte by wrapping DOMPurify.sanitize() around the output of marked.parse() rather than its input - matching the safe pattern already used in NotebookView.svelte:77. The vendor advisory with full details is at https://github.com/open-webui/open-webui/security/advisories/GHSA-fq3v-xjjx-95rc. For deployments unable to upgrade immediately, two compensating controls can reduce exposure: first, restrict write access to the 'Pending User Overlay Content' admin setting to only the most trusted administrator accounts, eliminating the opportunity for a compromised admin to plant the payload; second, disable the 'Default User Role: pending' setting if pending-user workflows are not operationally required, which prevents the vulnerable overlay from rendering entirely. Neither workaround eliminates the underlying code defect - they only remove preconditions for exploitation - and both should be treated as temporary mitigations pending upgrade.
Same weakness CWE-79 – Cross-site Scripting (XSS)
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-30610
GHSA-fq3v-xjjx-95rc