{"schema_version":"1.9.0","id":"GHSA-2658-85cv-9wwq","published":"2026-10-19T17:01:20Z","modified":"2026-10-19T17:15:04.101718667Z","aliases":["CVE-2026-111059"],"summary":"Mercury: Stored / DOM Cross-Site Scripting (XSS): mr.Markdown renders untrusted content as raw, unsanitized HTML","details":"## Summary\n\nMercury turns Jupyter notebooks into shared web apps/dashboards. The `mr.Markdown`\nwidget converts its input to HTML with **python-markdown and no sanitization\nstep**. `MarkdownWidget` is an `ipywidgets.HTML` widget, whose value is written to\nthe page as **raw HTML (via `innerHTML`) with no sanitization**, so the result\nreaches the DOM verbatim. When an app passes **untrusted data** (database rows,\nuser submissions, uploaded file contents, or an external/LLM API response) to\n`mr.Markdown`, HTML/JS in that data executes in the browser of **whoever views the\napp** — a data-driven stored/DOM-based XSS.\n\nThis is a library-provided rendering sink that fails to neutralize output; it is\ntriggered by ordinary, reasonable application code, not by an author mistake.\n\n## Root cause\n\n`mercury/md.py` — `MarkdownWidget._to_html()` (verified at v3.2.6, lines ~57–65):\n\n```python\ndef _to_html(self, text: str) -> str:\n if md_lib is not None:\n body = md_lib.markdown(text) # python-markdown: passes raw HTML through\n else:\n body = f\"
{escape(text)}\" # escape() is ONLY the md-lib-absent fallback\n return self._apply_inline_theme(body) # regex styling — adds style=, removes nothing\n```\n\nTwo neutralization failures compound:\n\n1. **Markdown → HTML is unsanitized.** `python-markdown` passes raw HTML through by\n default: `