# Open WebUI: Same-origin XSS to account takeover via terminal file-preview iframe hardcoding allow-same-origin

Open WebUI's terminal file-preview iframe hardcoded allow-same-origin with allow-scripts on a same-origin path, letting a malicious HTML file read the victim's localStorage token and take over the account.

- Payout: Research
- Severity: high
- Program: Open WebUI
- Found by: manus-use
- Disclosed: 2026-08-04
- CVE: CVE-2026-70486
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-3xpf-xq7r-v8c5
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-3xpf-xq7r-v8c5
- Tags: area:web domain:web impact:ato impact:privesc kind:research platform:ghsa sev:high tech:nodejs technique:incomplete-fix technique:source-review vuln:xss

**Target:** Terminal file-preview iframe served via /api/v1/terminals/{id}/files/serve/
**Root cause:** The serveUrl branch of FilePreview.svelte used a static iframe sandbox string containing both allow-scripts and allow-same-origin, and the terminal proxy serves files from the application's own origin with no CSP by default, so the sandbox granted no isolation. The srcdoc branch had already been hardened to make same-origin opt-in, but this branch ignored the iframeSandboxAllowSameOrigin setting.
**Steps:**
1. Configure a terminal server reachable by the victim (TERMINAL_SERVER_CONNECTIONS), leave TERMINAL_PROXY_HEADERS unset.
2. As a normal authenticated user, write an HTML file to the terminal server containing a script that reads `window.parent.localStorage.token` and exfiltrates it.
3. Trigger a `display_file` tool call for that file so the chat handler opens the preview on the `terminal:display_file` event without a click.
4. The preview loads from `/api/v1/terminals/{id}/files/serve/...` at the app origin with allow-same-origin + allow-scripts, the script reaches the parent, reads the token and exfiltrates it.
**How found:** Compared the two preview paths in FilePreview.svelte: the srcdoc branch had been hardened (same-origin opt-in plus injected CSP) while the newer serveUrl branch kept a static sandbox string with allow-same-origin baked in; noted the terminal proxy is mounted on the app's own origin and adds no CSP unless the operator configured one, leaving the sandbox as the only boundary.
**Exploitation:** Attacker with a normal account and terminal access plants a malicious HTML file, then induces a display_file event (via prompt injection or social step) so the victim's chat auto-opens the preview; the script runs at the application origin, reads the session token from localStorage and exfiltrates it for full account takeover. Admin or workspace.functions holders escalate to server-side code execution via Functions.
**Escalation:** Stolen token of an admin or a user with workspace.functions extends takeover to server-side code execution through Functions.
**Insight:** When a component grows a second rendering path, diff its security controls against the already-hardened path: a static sandbox attribute copied from an older version silently re-grants allow-same-origin. Also check whether proxied content is served from the app's own origin with no global CSP, which makes any same-origin sandbox grant fatal.

