Every CVE Search/ Get API key

CVE-2026-111059

Mercury: Stored / DOM Cross-Site Scripting (XSS): mr.Markdown renders untrusted content as raw, unsanitized HTML

6.1 MediumFix available

Mercury is a Python framework for sharing Jupyter notebooks as web applications and dashboards. From 3.0.0 until 3.2.11, mr.Markdown converts attacker-controlled Markdown to HTML without sanitizing raw HTML before assigning it to an ipywidgets.HTML sink. Event-handler attributes and other active HTML can therefore reach the DOM and execute when another user views the application. This issue is fixed in version 3.2.11.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
None
User interaction
Required

What an attacker can affect

Confidentiality
Low
Integrity
Low
Availability
None

Exploit likelihood

0.28%Unchanged since Oct 20, 2026
90%50%10%1%

What each source reports

CVE List Score used 6.1

Mercury: Stored / DOM Cross-Site Scripting (XSS): mr.Markdown renders untrusted content as raw, unsanitized HTML

Mercury is a Python framework for sharing Jupyter notebooks as web applications and dashboards. From 3.0.0 until 3.2.11, mr.Markdown converts attacker-controlled Markdown to HTML without sanitizing raw HTML before assigning it to an ipywidgets.HTML sink. Event-handler attributes and other active HTML can therefore reach the DOM and execute when another user views the application. This issue is fixed in version 3.2.11.

Record
CVE-2026-111059
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Weakness
CWE-79, CWE-116
Updated
2026-11-02
EUVDEUVD-2026-104453 6.1

Mercury is a Python framework for sharing Jupyter notebooks as web applications and dashboards. From 3.0.0 until 3.2.11, mr.Markdown converts attacker-controlled Markdown to HTML without sanitizing raw HTML before assigning it to an ipywidgets.HTML sink. Event-handler attributes and other active HTML can therefore reach the DOM and execute when another user views the application. This issue is fixed in version 3.2.11.

Record
EUVD-2026-104453
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-2658-85cv-9wwq 6.1

Mercury: Stored / DOM Cross-Site Scripting (XSS): mr.Markdown renders untrusted content as raw, unsanitized HTML

Summary

Mercury turns Jupyter notebooks into shared web apps/dashboards. The mr.Markdown widget converts its input to HTML with python-markdown and no sanitization step. MarkdownWidget is an ipywidgets.HTML widget, whose value is written to the page as raw HTML (via innerHTML) with no sanitization, so the result reaches the DOM verbatim. When an app passes untrusted data (database rows, user submissions, uploaded file contents, or an external/LLM API response) to mr.Markdown, HTML/JS in that data executes in the browser of whoever views the app — a data-driven stored/DOM-based XSS.

This is a library-provided rendering sink that fails to neutralize output; it is triggered by ordinary, reasonable application code, not by an author mistake.

Root cause

mercury/md.py — MarkdownWidget._to_html() (verified at v3.2.6, lines ~57–65):

def _to_html(self, text: str) -> str:
    if md_lib is not None:
        body = md_lib.markdown(text)          # python-markdown: passes raw HTML through
    else:
        body = f"<pre>{escape(text)}</pre>"   # escape() is ONLY the md-lib-absent fallback
    return self._apply_inline_theme(body)     # regex styling — adds style=, removes nothing

Two neutralization failures compound:

  1. Markdown → HTML is unsanitized. python-markdown passes raw HTML through by default: <img src=x onerror=…>, <svg onload=…>, and javascript: links survive verbatim. There is no bleach/nh3 allowlist anywhere in the package. _apply_inline_theme() only injects inline style= attributes via regex; it is not a sanitizer and removes nothing. escape() runs only in the md-lib-absent fallback.

  2. The rendering sink is unsanitized innerHTML. MarkdownWidget (mercury/md.py:21) subclasses ipywidgets.HTML. The ipywidgets HTML widget renders its value into the DOM as raw HTML via innerHTML, with no sanitization (this is the widget's design). Event-handler payloads such as <img src=x onerror=…> therefore execute when the widget is displayed.

On the "sanitize" that exists: mercury_app/notebook_sanitize.py (sanitize_notebook_for_mercury_runtime) and packages/lab/src/mercury/save.ts (sanitizeMercuryNotebookContent) strip runtime metadata and clear code-cell saved outputs. They do not neutralize HTML/JS, and they do not touch widget values or markdown source. "Sanitize" here is notebook hygiene, not XSS defense.

Threat model (attacker → victim, not self-inflicted)

The CVE-relevant framing is data-driven: the app author writes ordinary code, and the data is attacker-controlled.

import mercury as mr
# `row` comes from a DB / user submission / uploaded file / external API —
# i.e. NOT authored by the app developer:
mr.Markdown(row["comment"])

The attacker supplies the untrusted value (posts the comment, uploads the file, poisons the API/RAG source). A different user — another app user, a reviewer, an admin — then opens the app, and the payload executes in their browser. The vulnerable component (Mercury's server-side widget rendering) and the impacted component (the victim's browser session in the app origin) differ, which is the classic stored-XSS attacker/victim separation.

Amplifier — session sharing (--keep-session). Mercury can serve one shared kernel session to all viewers (mercury run … --keep-session, README "Arguments": "share the same session between multiple users … others will see this"). It is opt-in and defaults to off (keepSession=False in mercury_app/handlers.py), so it is not required for the base case — any app that renders one user's untrusted data to another user is already vulnerable. When enabled, content one viewer submits is rendered into the session other viewers see, extending this to cross-user delivery. Authentication is opt-in and off by default: a free password gate exists (`mercury run…

Record
GHSA-2658-85cv-9wwq
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Weakness
CWE-79, CWE-116
Updated
2026-11-02
NVD 6.1

Mercury is a Python framework for sharing Jupyter notebooks as web applications and dashboards. From 3.0.0 until 3.2.11, mr.Markdown converts attacker-controlled Markdown to HTML without sanitizing raw HTML before assigning it to an ipywidgets.HTML sink. Event-handler attributes and other active HTML can therefore reach the DOM and execute when another user views the application. This issue is fixed in version 3.2.11.

Record
CVE-2026-111059
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Weakness
CWE-79, CWE-116
Updated
2026-11-02
OSV 6.1

Mercury: Stored / DOM Cross-Site Scripting (XSS): mr.Markdown renders untrusted content as raw, unsanitized HTML

Mercury is a Python framework for sharing Jupyter notebooks as web applications and dashboards. From 3.0.0 until 3.2.11, mr.Markdown converts attacker-controlled Markdown to HTML without sanitizing raw HTML before assigning it to an ipywidgets.HTML sink. Event-handler attributes and other active HTML can therefore reach the DOM and execute when another user views the application. This issue is fixed in version 3.2.11.

Record
CVE-2026-111059
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Weakness
CWE-116, CWE-79
Updated
2026-11-02
OSVGHSA-2658-85cv-9wwq 6.1

Mercury: Stored / DOM Cross-Site Scripting (XSS): mr.Markdown renders untrusted content as raw, unsanitized HTML

Summary

Mercury turns Jupyter notebooks into shared web apps/dashboards. The mr.Markdown widget converts its input to HTML with python-markdown and no sanitization step. MarkdownWidget is an ipywidgets.HTML widget, whose value is written to the page as raw HTML (via innerHTML) with no sanitization, so the result reaches the DOM verbatim. When an app passes untrusted data (database rows, user submissions, uploaded file contents, or an external/LLM API response) to mr.Markdown, HTML/JS in that data executes in the browser of whoever views the app — a data-driven stored/DOM-based XSS.

This is a library-provided rendering sink that fails to neutralize output; it is triggered by ordinary, reasonable application code, not by an author mistake.

Root cause

mercury/md.py — MarkdownWidget._to_html() (verified at v3.2.6, lines ~57–65):

def _to_html(self, text: str) -> str:
    if md_lib is not None:
        body = md_lib.markdown(text)          # python-markdown: passes raw HTML through
    else:
        body = f"<pre>{escape(text)}</pre>"   # escape() is ONLY the md-lib-absent fallback
    return self._apply_inline_theme(body)     # regex styling — adds style=, removes nothing

Two neutralization failures compound:

  1. Markdown → HTML is unsanitized. python-markdown passes raw HTML through by default: <img src=x onerror=…>, <svg onload=…>, and javascript: links survive verbatim. There is no bleach/nh3 allowlist anywhere in the package. _apply_inline_theme() only injects inline style= attributes via regex; it is not a sanitizer and removes nothing. escape() runs only in the md-lib-absent fallback.

  2. The rendering sink is unsanitized innerHTML. MarkdownWidget (mercury/md.py:21) subclasses ipywidgets.HTML. The ipywidgets HTML widget renders its value into the DOM as raw HTML via innerHTML, with no sanitization (this is the widget's design). Event-handler payloads such as <img src=x onerror=…> therefore execute when the widget is displayed.

On the "sanitize" that exists: mercury_app/notebook_sanitize.py (sanitize_notebook_for_mercury_runtime) and packages/lab/src/mercury/save.ts (sanitizeMercuryNotebookContent) strip runtime metadata and clear code-cell saved outputs. They do not neutralize HTML/JS, and they do not touch widget values or markdown source. "Sanitize" here is notebook hygiene, not XSS defense.

Threat model (attacker → victim, not self-inflicted)

The CVE-relevant framing is data-driven: the app author writes ordinary code, and the data is attacker-controlled.

import mercury as mr
# `row` comes from a DB / user submission / uploaded file / external API —
# i.e. NOT authored by the app developer:
mr.Markdown(row["comment"])

The attacker supplies the untrusted value (posts the comment, uploads the file, poisons the API/RAG source). A different user — another app user, a reviewer, an admin — then opens the app, and the payload executes in their browser. The vulnerable component (Mercury's server-side widget rendering) and the impacted component (the victim's browser session in the app origin) differ, which is the classic stored-XSS attacker/victim separation.

Amplifier — session sharing (--keep-session). Mercury can serve one shared kernel session to all viewers (mercury run … --keep-session, README "Arguments": "share the same session between multiple users … others will see this"). It is opt-in and defaults to off (keepSession=False in mercury_app/handlers.py), so it is not required for the base case — any app that renders one user's untrusted data to another user is already vulnerable. When enabled, content one viewer submits is rendered into the session other viewers see, extending this to cross-user delivery. Authentication is opt-in and off by default: a free password gate exists (`mercury run…

Record
GHSA-2658-85cv-9wwq
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Weakness
CWE-116, CWE-79
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
mercury
PyPI
3.0.0 up to 3.2.11
3.2.11
mercury
PyPI
3.0.0 up to 3.2.11, plus listed versions
3.0.13.0.23.1.03.1.13.1.2
Show 13 moreShow less
3.1.43.1.53.2.03.2.13.2.103.2.23.2.33.2.43.2.53.2.63.2.73.2.83.2.9
3.2.11

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-111059 is a medium vulnerability (CVSS 6.1) in Mljar Mercury, published October 19, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.28%, the 21st percentile. Fixed in mercury 3.2.11.

References

This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open