VAITP Dataset

← Back to the dataset

CVE-2026-21871

XSS in NiceGUI's ui.navigate.history allows arbitrary JavaScript execution.

  • CVSS 6.1
  • CWE-79
  • Input Validation and Sanitization
  • Remote

NiceGUI is a Python-based UI framework. From versions 2.13.0 to 3.4.1, there is a XSS risk in NiceGUI when developers pass attacker-controlled strings into ui.navigate.history.push() or ui.navigate.history.replace(). These helpers are documented as History API wrappers for updating the browser URL without page reload. However, if the URL argument is embedded into generated JavaScript without proper escaping, a crafted payload can break out of the intended string context and execute arbitrary JavaScript in the victim’s browser. Applications that do not pass untrusted input into ui.navigate.history.push/replace are not affected. This issue has been patched in version 3.5.0.

CVSS base score
6.1
Published
2026-01-08
OWASP
A03 Injection
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Cross-Site Scripting (XSS)
Accessibility scope
Remote
Impact
Arbitrary Code Execution
Affected component
NiceGUI
Fixed by upgrading
Yes

Solution

Upgrade NiceGUI to version 3.5.0 or later.

Vulnerable code sample

from nicegui import ui

# This code represents a vulnerable application.
# To demonstrate the vulnerability, run this script with a vulnerable
# version of NiceGUI (e.g., `pip install nicegui==3.4.1`).
#
# In the browser, enter the following payload into the input field:
# ');alert('XSS');//
#
# Clicking the button will execute the alert() in the browser,
# demonstrating the Cross-Site Scripting (XSS) vulnerability.

path_input = ui.input(label='Path to navigate to')

ui.button(
    'Update Browser History',
    on_click=lambda: ui.navigate.history.push(path_input.value)
)

ui.run()

Patched code sample

from nicegui import ui

# The fix for the specified vulnerability (likely a typo for a real CVE like
# GHSA-5pfj-x39x-gxv5) is not in application-level code but within the NiceGUI
# library itself (version >= 3.5.0). The internal fix ensures that any string
# passed to ui.navigate.history.push or ui.navigate.history.replace is
# properly serialized and cannot break out of its string context to execute
# arbitrary JavaScript.
#
# This code demonstrates the SAFE behavior in a patched version of NiceGUI.
#
# HOW TO VERIFY THE FIX:
# 1. Run this code with a patched NiceGUI version (>= 3.5.0).
# 2. Enter the XSS payload into the input field:    ');alert('XSS');//
# 3. Click the button.
#
# EXPECTED RESULT (FIXED BEHAVIOR):
# The browser's URL will change to include the payload as a URL-encoded string.
# No JavaScript alert will be executed.
#
# VULNERABLE RESULT (for versions < 3.5.0):
# A JavaScript alert box with the message 'XSS' would appear.

@ui.page('/show/{path_part}')
def show_page(path_part: str):
    """A page to navigate to, demonstrating the path is handled safely."""
    ui.label('Safely navigated. The path parameter received is:').classes('text-h6')
    ui.code(path_part)
    ui.link('Go back to main page', '/')

# An input field to represent an attacker-controlled or untrusted string.
user_input = ui.input(
    label="Enter a string for the URL path",
    placeholder="');alert('XSS');//"
).classes('w-96')

def trigger_vulnerable_pattern():
    """
    This function uses the API pattern described in the CVE. It passes an
    untrusted string directly to a navigation function. In a patched
    NiceGUI library, this operation is safe.
    """
    path_value = user_input.value
    target_path = f'/show/{path_value}'

    # This is the function call mentioned in the CVE. In vulnerable versions,
    # the 'target_path' argument was not properly escaped on the frontend,
    # leading to XSS. The fix is internal to how NiceGUI processes this call.
    # Note: history.push() only changes the URL in the browser's history
    # without reloading the page or redrawing the UI in this context.
    ui.navigate.history.push(target_path)

    ui.notify(f"Called ui.navigate.history.push with '{target_path}'")
    ui.label(f"The URL should now end with the encoded path. "
             f"If no alert appeared, the fix is working.")

ui.button('Demonstrate Fix with ui.navigate.history.push',
          on_click=trigger_vulnerable_pattern)

ui.run()

Payload

');alert(document.domain)//

Cite this entry

@misc{vaitp:cve202621871,
  title        = {{XSS in NiceGUI's ui.navigate.history allows arbitrary JavaScript execution.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-21871},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-21871/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::