Any authenticated user can store a javascript: URL in the embed_url field of a chat citation and that chat with another user. When the second user opens the shared chat and clicks the citation, the URL is passed straight to window.open , executing attacker script in the Open WebUI origin. The frontend never checks the URL scheme before opening it, so the attacker's script runs with the victim's session and can read localStorage.token and act as the victim.
Default configuration, fresh database, no special settings. The attacker is any authenticated user who can create and a chat (no admin rights, no workspace.* permission). The victim is any user who opens the shared read-only chat ( /s/{share_id} ), expands the sources, and clicks the malicious citation. The embed_url value is set by editing the attacker's own chat through the normal chat update API, which stores the attacker-controlled citation structure.
Script executes in the application origin as the victim, so the attacker can exfiltrate localStorage.token (the JWT used for every API call) and then act with the victim's permissions, including reading the victim's private chats. Exploitation requires the victim to click the citation in the shared chat. If the victim is an administrator, the account compromise extends to admin capabilities. Only the citation-embed click path is affected; citations whose embed_url is a normal http/https link are unaffected.
Fixed in 0.11.4 by PR #29701 . The citation click handler now requires the embed_url to be an http / https URL (or protocol-relative) before it is opened or handed to the embed panel; any other value, including a javascript: URL, falls back to the citation modal, which renders the source safely. Upgrading to 0.11.4 fully resolves the issue with no further configuration.
src/lib/components/chat/Messages/Citations.svelte ( showSourceModal ) read citations[index].source.embed_url and, when the chat is read-only, called window.open(embedUrl, '_blank') directly. The value comes from the stored chat, which a user fully controls through the chat update API, and no URL-scheme validation stood between the stored value and the sink. The read-only window.open branch shipped from 0.6.33 onward, so every build from 0.6.33 through 0.11.3 carried the code.
The reporter reproduced with two normal accounts on a default 0.11.3 instance: the attacker stored a citation with embed_url set to a javascript: payload that fetched localStorage.token to a listener, shared the chat with the victim, and on the victim clicking the citation the token was exfiltrated and then used to read a private victim chat that the attacker account could not access directly. Independently confirmed in Chromium that window.open with a javascript: URL and a _blank target runs in the opening page's origin and can read and send its localStorage.token . On 0.11.4 the guard rejects javascript: and data: values and admits only http, https and protocol-relative URLs to that path.
Reported by @zyakir13 . Fix by @Classic298 .
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
