CVE-2026-111059
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.
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
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 Medium
CVSS: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 Medium
CVSS: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:
-
Markdown → HTML is unsanitized.
python-markdownpasses raw HTML through by default:<img src=x onerror=…>,<svg onload=…>, andjavascript:links survive verbatim. There is nobleach/nh3allowlist anywhere in the package._apply_inline_theme()only injects inlinestyle=attributes via regex; it is not a sanitizer and removes nothing.escape()runs only in the md-lib-absent fallback. -
The rendering sink is unsanitized
innerHTML.MarkdownWidget(mercury/md.py:21) subclassesipywidgets.HTML. The ipywidgets HTML widget renders itsvalueinto the DOM as raw HTML viainnerHTML, 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 Medium
CVSS: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 Medium
CVSS: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 Medium
CVSS: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:
-
Markdown → HTML is unsanitized.
python-markdownpasses raw HTML through by default:<img src=x onerror=…>,<svg onload=…>, andjavascript:links survive verbatim. There is nobleach/nh3allowlist anywhere in the package._apply_inline_theme()only injects inlinestyle=attributes via regex; it is not a sanitizer and removes nothing.escape()runs only in the md-lib-absent fallback. -
The rendering sink is unsanitized
innerHTML.MarkdownWidget(mercury/md.py:21) subclassesipywidgets.HTML. The ipywidgets HTML widget renders itsvalueinto the DOM as raw HTML viainnerHTML, 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 Medium
CVSS: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
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE 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
github.com/mljar/mercury/security/advisories/GHSA-2658-85cv-9wwq
github.com/mljar/mercury/commit/c798a85924e6614c6fea7afe5a560e4554a1d09b
github.com/mljar/mercury/releases/tag/v3.2.11
github.com/advisories/GHSA-2658-85cv-9wwq
github.com/CVEProject/cvelistV5/tree/main/cves/2026/111xxx/CVE-2026-111059.json
nvd.nist.gov/vuln/detail/CVE-2026-111059
github.com/mljar/mercury
Left out here: the rest of 2 source descriptions. The JSON record has them.