# NiceGUI apps which use `ui.sub_pages` vulnerable to zero-click XSS

NiceGUI ui.sub_pages interpolates the URL fragment into JS unescaped, so a cross-site iframe setting a crafted hash triggers zero-click XSS.

- Payout: Research
- Severity: high
- Program: NiceGUI
- Found by: evnchn
- Disclosed: 2026-01-08
- CVE: CVE-2026-21873
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-mhpg-c27v-6mxr
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-mhpg-c27v-6mxr
- Tags: area:web domain:web impact:integrity kind:research platform:ghsa sev:high tech:python technique:chained technique:source-review vuln:xss

**Target:** Python web UI framework (ui.sub_pages SPA router, browser JS)
**Root cause:** _handle_scrolling passes the attacker-controlled URL fragment into _scroll_to_fragment, which interpolates it into a JS string without escaping, so a quote in the fragment breaks out into executable JS.
**Steps:**
1. Load a page using `ui.sub_pages` inside an iframe: `<iframe id="myiframe" src="https://nicegui.io" onload="triggerXSS()">`
2. On load, set the iframe src to a crafted fragment: `myiframe.src = "https://nicegui.io#x');alert(document.domain)//"`
3. pushstate fires handleStateEvent -> sub_pages_open -> SubPagesRouter._handle_open -> SubPages._show -> _handle_scrolling -> _scroll_to_fragment, executing the injected JS
**How found:** Traced the pushstate event chain from sub_pages.js through SubPagesRouter._handle_open into SubPages._show, followed the if-logic/debug prints to _handle_scrolling, and identified _scroll_to_fragment as the sink interpolating the fragment into JS.
**Exploitation:** Attacker hosts a page with an iframe pointing at the victim NiceGUI app; onload rewrites the iframe src to append a fragment containing `');alert(document.domain)//`, which escapes the JS string in _scroll_to_fragment and runs in the app's origin with no user click.
**Insight:** SPA routers that read location.hash and pass it into JS string templates are a zero-click XSS sink reachable cross-origin via iframe src rewriting; grep for fragment/hash values interpolated into script strings.

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

**Iframe PoC triggering zero-click XSS via fragment**

```
<iframe id="myiframe" src="https://nicegui.io" width="100%" height="600px" onload="triggerXSS()"></iframe>
    <script>
      function triggerXSS() {
        if (!myiframe.src.includes("#")) {
          myiframe.src = "https://nicegui.io#x');alert(document.domain)//";
```

