VYPR
Moderate severityNVD Advisory· Published Feb 24, 2026· Updated Feb 26, 2026

NiceGUI has XSS via Code Injection

CVE-2026-27156

Description

NiceGUI is a Python-based UI framework. Prior to version 3.8.0, several NiceGUI APIs that execute methods on client-side elements (Element.run_method(), AgGrid.run_grid_method(), EChart.run_chart_method(), and others) use an eval() fallback in the JavaScript-side runMethod() function. When user-controlled input is passed as the method name, an attacker can inject arbitrary JavaScript that executes in the victim's browser. Additionally, Element.run_method() and Element.get_computed_prop() used string interpolation instead of json.dumps() for the method/property name, allowing quote injection to break out of the intended string context. Version 3.8.0 contains a fix.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
niceguiPyPI
< 3.8.03.8.0

Affected products

1

Patches

1
1861f59cc374

Merge commit from fork

https://github.com/zauberzeug/niceguiFalko SchindlerFeb 24, 2026via ghsa
5 files changed · +77 26
  • nicegui/element.py+6 2 modified
    @@ -412,7 +412,9 @@ def run_method(self, name: str, *args: Any, timeout: float = 1) -> AwaitableResp
             """
             if not core.loop:
                 return NullResponse()
    -        return self.client.run_javascript(f'return runMethod({self.id}, "{name}", {json.dumps(args)})', timeout=timeout)
    +        return self.client.run_javascript(
    +            f'return runMethod({self.id}, {json.dumps(name)}, {json.dumps(args)})', timeout=timeout,
    +        )
     
         def get_computed_prop(self, prop_name: str, *, timeout: float = 1) -> AwaitableResponse:
             """Return a computed property.
    @@ -424,7 +426,9 @@ def get_computed_prop(self, prop_name: str, *, timeout: float = 1) -> AwaitableR
             """
             if not core.loop:
                 return NullResponse()
    -        return self.client.run_javascript(f'return getComputedProp({self.id}, "{prop_name}")', timeout=timeout)
    +        return self.client.run_javascript(
    +            f'return getComputedProp({self.id}, {json.dumps(prop_name)})', timeout=timeout,
    +        )
     
         def ancestors(self, *, include_self: bool = False) -> Iterator[Element]:
             """Iterate over the ancestors of the element.
    
  • nicegui/static/nicegui.js+12 10 modified
    @@ -123,19 +123,21 @@ function runMethod(target, method_name, args) {
       if (typeof target === "object") {
         if (method_name in target) {
           return target[method_name](...args);
    -    } else {
    -      return eval(method_name)(target, ...args);
         }
    -  }
    -  const element = getElement(target);
    -  if (element === null || element === undefined) return;
    -  if (method_name in element) {
    -    return element[method_name](...args);
    -  } else if (method_name in (element.$refs.qRef || [])) {
    -    return element.$refs.qRef[method_name](...args);
       } else {
    -    return eval(method_name)(element, ...args);
    +    const element = getElement(target);
    +    if (element === null || element === undefined) return;
    +    if (method_name in element) {
    +      return element[method_name](...args);
    +    } else if (method_name in (element.$refs.qRef || [])) {
    +      return element.$refs.qRef[method_name](...args);
    +    }
    +  }
    +  let msg = `Method "${method_name}" not found.`;
    +  if (method_name.includes("=>") || method_name.startsWith("(")) {
    +    msg += " To run arbitrary JavaScript, use ui.run_javascript() instead.";
       }
    +  logAndEmit("error", msg);
     }
     
     function getComputedProp(target, prop_name) {
    
  • tests/test_aggrid.py+15 9 modified
    @@ -293,19 +293,25 @@ def page():
         screen.should_contain('42')
     
     
    -def test_run_method_with_function(screen: Screen):
    +def test_run_grid_method_xss(screen: Screen):
         @ui.page('/')
         def page():
    -        grid = ui.aggrid({'columnDefs': [{'field': 'name'}], 'rowData': [{'name': 'Alice'}, {'name': 'Bob'}]})
    -
    -        async def print_row(index: int) -> None:
    -            ui.label(f'Row {index}: {await grid.run_grid_method(f"(g) => g.getDisplayedRowAtIndex({index}).data")}')
    -
    -        ui.button('Print Row 0', on_click=lambda: print_row(0))
    +        grid = ui.aggrid({
    +            'columnDefs': [{'field': 'name'}],
    +            'rowData': [{'name': 'Alice'}, {'name': 'Bob'}],
    +        })
    +        ui.button('XSS 1', on_click=lambda: grid.run_grid_method('console.error("X" + "SS")'))
    +        ui.button('XSS 2', on_click=lambda: grid.run_grid_method('x", console.error("X" + "SS"), "y'))
     
    +    screen.allowed_js_errors.append('Method "console.error("X" + "SS")" not found.')
    +    screen.allowed_js_errors.append('Method "x", console.error("X" + "SS"), "y" not found.')
         screen.open('/')
    -    screen.click('Print Row 0')
    -    screen.should_contain("Row 0: {'name': 'Alice'}")
    +    screen.click('XSS 1')
    +    screen.click('XSS 2')
    +    screen.wait(1)
    +    assert 'XSS' not in screen.render_js_logs()
    +    screen.assert_py_logger('ERROR', 'Method "console.error("X" + "SS")" not found.')
    +    screen.assert_py_logger('ERROR', 'Method "x", console.error("X" + "SS"), "y" not found.')
     
     
     def test_get_client_data(screen: Screen):
    
  • tests/test_element.py+32 0 modified
    @@ -241,6 +241,38 @@ def page():
         screen.should_contain('<b>Bold 2</b>, `code`, copy&paste, multi\nline')
     
     
    +def test_run_method_xss(screen: Screen):
    +    @ui.page('/')
    +    def page():
    +        ui.button('XSS 1', on_click=lambda e: e.sender.run_method('console.error("X" + "SS")'))
    +        ui.button('XSS 2', on_click=lambda e: e.sender.run_method('x", console.error("X" + "SS"), "y'))
    +
    +    screen.allowed_js_errors.append('Method "console.error("X" + "SS")" not found.')
    +    screen.allowed_js_errors.append('Method "x", console.error("X" + "SS"), "y" not found.')
    +    screen.open('/')
    +    screen.click('XSS 1')
    +    screen.click('XSS 2')
    +    screen.wait(1)
    +    assert 'XSS' not in screen.render_js_logs()
    +    screen.assert_py_logger('ERROR', 'Method "console.error("X" + "SS")" not found.')
    +    screen.assert_py_logger('ERROR', 'Method "x", console.error("X" + "SS"), "y" not found.')
    +
    +
    +def test_get_computed_prop_xss(screen: Screen):
    +    @ui.page('/')
    +    def page():
    +        ui.button('XSS 1', on_click=lambda e: e.sender.get_computed_prop('console.error("X" + "SS")'))
    +        ui.button('XSS 2', on_click=lambda e: e.sender.get_computed_prop('x", console.error("X" + "SS"), "y'))
    +
    +    screen.allowed_js_errors.append('Method "console.error("X" + "SS")" not found.')
    +    screen.allowed_js_errors.append('Method "x", console.error("X" + "SS"), "y" not found.')
    +    screen.open('/')
    +    screen.click('XSS 1')
    +    screen.click('XSS 2')
    +    screen.wait(1)
    +    assert 'XSS' not in screen.render_js_logs()
    +
    +
     def test_default_props(screen: Screen):
         @ui.page('/')
         def page():
    
  • website/documentation/content/aggrid_documentation.py+12 5 modified
    @@ -250,18 +250,25 @@ def aggrid_run_row_method():
                   on_click=lambda: grid.run_row_method('Alice', 'setDataValue', 'age', 99))
     
     
    -@doc.demo('Filter return values', '''
    -    You can filter the return values of method calls by passing string that defines a JavaScript function.
    -    This demo runs the grid method "getDisplayedRowAtIndex" and returns the "data" property of the result.
    -''')
    +@doc.ui
     def aggrid_filter_return_values():
    +    ui.link_target('filter_return_values')
    +
    +
    +@doc.demo('Access grid API via JavaScript', '''
    +    You can access the AG Grid API directly via `ui.run_javascript()` for more complex operations.
    +    This demo accesses the grid API to get the first displayed row's data.
    +''')
    +def aggrid_access_api_via_javascript():
         grid = ui.aggrid({
             'columnDefs': [{'field': 'name'}],
             'rowData': [{'name': 'Alice'}, {'name': 'Bob'}],
         })
     
         async def get_first_name() -> None:
    -        row = await grid.run_grid_method('g => g.getDisplayedRowAtIndex(0).data')
    +        row = await ui.run_javascript(
    +            f'return getElement({grid.id}).api.getDisplayedRowAtIndex(0).data',
    +        )
             ui.notify(row['name'])
     
         ui.button('Get First Name', on_click=get_first_name)
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.