HTML injection in Jupyter Notebook and JupyterLab leading to DOM Clobbering
Description
jupyterlab is an extensible environment for interactive and reproducible computing, based on the Jupyter Notebook Architecture. This vulnerability depends on user interaction by opening a malicious notebook with Markdown cells, or Markdown file using JupyterLab preview feature. A malicious user can access any data that the attacked user has access to as well as perform arbitrary requests acting as the attacked user. JupyterLab v3.6.8, v4.2.5 and Jupyter Notebook v7.2.2 have been patched to resolve this issue. Users are advised to upgrade. There is no workaround for the underlying DOM Clobbering susceptibility. However, select plugins can be disabled on deployments which cannot update in a timely fashion to minimise the risk. These are: 1. @jupyterlab/mathjax-extension:plugin - users will loose ability to preview mathematical equations. 2. @jupyterlab/markdownviewer-extension:plugin - users will loose ability to open Markdown previews. 3. @jupyterlab/mathjax2-extension:plugin (if installed with optional jupyterlab-mathjax2 package) - an older version of the mathjax plugin for JupyterLab 4.x. To disable these extensions run: ``jupyter labextension disable @jupyterlab/markdownviewer-extension:plugin && jupyter labextension disable @jupyterlab/mathjax-extension:plugin && jupyter labextension disable @jupyterlab/mathjax2-extension:plugin `` in bash.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
DOM Clobbering in JupyterLab's Markdown preview allows XSS, granting access to victim's data and actions.
Vulnerability
Overview
JupyterLab (and Jupyter Notebook) contain a DOM Clobbering vulnerability in the Markdown preview and Markdown cell rendering features. The root cause is that the HTML sanitizer used by JupyterLab previously allowed id and name attributes in sanitized output. By crafting Markdown with elements that define these attributes, an attacker can overwrite references to global DOM properties, enabling cross-site scripting (XSS) [1][3][4].
Exploitation
An attacker can embed malicious HTML in a Markdown cell of a notebook or a Markdown file viewed via the JupyterLab preview feature. No authentication is required beyond what the victim has; the attack simply requires the victim to open the crafted file. The attack does not need any special network position beyond serving the malicious file to the target [1].
Impact
Successful exploitation allows the attacker to execute arbitrary JavaScript in the context of the victim's JupyterLab session. This enables access to any data the victim can access (notebooks, files, API tokens) and allows performing arbitrary requests as the victim, effectively a full session takeover [1].
Mitigation
Patches are available in JupyterLab v3.6.8, v4.2.5, and Jupyter Notebook v7.2.2 [1]. For deployments that cannot update immediately, administrators can disable the vulnerable plugins (@jupyterlab/mathjax-extension:plugin, @jupyterlab/markdownviewer-extension:plugin, and optionally @jupyterlab/mathjax2-extension:plugin) via the jupyter labextension disable command, at the cost of losing mathematical equation previews and Markdown preview functionality [1]. The fix itself modifies the Sanitizer class to conditionally allow id and name attributes only when explicitly enabled, preventing DOM Clobbering [3][4].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
jupyterlabPyPI | < 3.6.8 | 3.6.8 |
notebookPyPI | >= 7.0.0, < 7.2.2 | 7.2.2 |
jupyterlabPyPI | >= 4.0.0, < 4.2.5 | 4.2.5 |
Affected products
10- osv-coords9 versionspkg:bitnami/jupyter-base-notebookpkg:bitnami/jupyterlabpkg:bitnami/jupyter-notebookpkg:pypi/jupyterlabpkg:pypi/notebookpkg:rpm/opensuse/python-jupyterlab&distro=openSUSE%20Leap%2015.5pkg:rpm/opensuse/python-jupyterlab&distro=openSUSE%20Leap%2015.6pkg:rpm/suse/python-jupyterlab&distro=SUSE%20Package%20Hub%2015%20SP5pkg:rpm/suse/python-jupyterlab&distro=SUSE%20Package%20Hub%2015%20SP6
>= 7.0.0+ 8 more
- (no CPE)range: >= 7.0.0
- (no CPE)range: < 4.2.5
- (no CPE)range: >= 7.0.0, < 7.2.2
- (no CPE)range: < 3.6.8
- (no CPE)range: >= 7.0.0, < 7.2.2
- (no CPE)range: < 2.2.10-bp156.3.3.1
- (no CPE)range: < 2.2.10-bp156.3.3.1
- (no CPE)range: < 2.2.10-bp156.3.3.1
- (no CPE)range: < 2.2.10-bp156.3.3.1
- jupyterlab/jupyterlabv5Range: notebook: >= 7.0.0, <= 7.2.2
Patches
206ad9de836f1Merge commit from fork
3 files changed · +35 −12
packages/apputils-extension/schema/sanitizer.json+6 −0 modified@@ -13,6 +13,12 @@ "type": "string" }, "default": ["http", "https", "ftp", "mailto", "tel"] + }, + "allowNamedProperties": { + "type": "boolean", + "title": "Allow named properties", + "description": "Whether to allow untrusted elements to include `name` and `id` attributes. These attributes are stripped by default to prevent DOM clobbering attacks.", + "default": false } }, "type": "object"
packages/apputils-extension/src/index.ts+4 −0 modified@@ -619,10 +619,14 @@ const sanitizer: JupyterFrontEndPlugin<ISanitizer> = { const allowedSchemes = setting.get('allowedSchemes').composite as Array< string >; + const allowNamedProperties = setting.get('allowNamedProperties') + .composite as boolean; if (allowedSchemes) { sanitizer.setAllowedSchemes(allowedSchemes); } + + sanitizer.setAllowNamedProperties(allowNamedProperties); }; // Wait for the application to be restored and
packages/apputils/src/sanitizer.ts+25 −12 modified@@ -435,6 +435,9 @@ class CssProp { * A class to sanitize HTML strings. */ export class Sanitizer implements ISanitizer { + constructor() { + this._options = this._generateOptions(); + } /** * Sanitize an HTML string. * @@ -458,7 +461,17 @@ export class Sanitizer implements ISanitizer { this._options.allowedSchemes = [...scheme]; } - private _options: sanitize.IOptions = { + /** + * Set the whether to allow `name` and `id` attributes. + */ + setAllowNamedProperties(allowNamedProperties: boolean): void { + this._allowNamedProperties = allowNamedProperties; + this._options = this._generateOptions(); + } + + private _allowNamedProperties: boolean = false; + private _options: sanitize.IOptions; + private _generateOptions = (): sanitize.IOptions => ({ // HTML tags that are allowed to be used. Tags were extracted from Google Caja allowedTags: [ 'a', @@ -573,7 +586,7 @@ export class Sanitizer implements ISanitizer { 'dir', 'draggable', 'hidden', - 'id', + ...(this._allowNamedProperties ? ['id'] : []), 'inert', 'itemprop', 'itemref', @@ -590,7 +603,7 @@ export class Sanitizer implements ISanitizer { 'coords', 'href', 'hreflang', - 'name', + ...(this._allowNamedProperties ? ['name'] : []), 'rel', 'shape', 'tabindex', @@ -624,7 +637,7 @@ export class Sanitizer implements ISanitizer { 'data-commandlinker-args', 'data-commandlinker-command', 'disabled', - 'name', + ...(this._allowNamedProperties ? ['name'] : []), 'tabindex', 'type', 'value' @@ -655,7 +668,7 @@ export class Sanitizer implements ISanitizer { 'autocomplete', 'enctype', 'method', - 'name', + ...(this._allowNamedProperties ? ['name'] : []), 'novalidate' ], h1: ['align'], @@ -680,7 +693,7 @@ export class Sanitizer implements ISanitizer { 'height', 'hspace', 'ismap', - 'name', + ...(this._allowNamedProperties ? ['name'] : []), 'src', 'usemap', 'vspace', @@ -701,7 +714,7 @@ export class Sanitizer implements ISanitizer { 'maxlength', 'min', 'multiple', - 'name', + ...(this._allowNamedProperties ? ['name'] : []), 'placeholder', 'readonly', 'required', @@ -717,13 +730,13 @@ export class Sanitizer implements ISanitizer { label: ['accesskey', 'for'], legend: ['accesskey', 'align'], li: ['type', 'value'], - map: ['name'], + map: this._allowNamedProperties ? ['name'] : [], menu: ['compact', 'label', 'type'], meter: ['high', 'low', 'max', 'min', 'value'], ol: ['compact', 'reversed', 'start', 'type'], optgroup: ['disabled', 'label'], option: ['disabled', 'label', 'selected', 'value'], - output: ['for', 'name'], + output: ['for', ...(this._allowNamedProperties ? ['name'] : [])], p: ['align'], pre: ['width'], progress: ['max', 'min', 'value'], @@ -732,7 +745,7 @@ export class Sanitizer implements ISanitizer { 'autocomplete', 'disabled', 'multiple', - 'name', + ...(this._allowNamedProperties ? ['name'] : []), 'required', 'size', 'tabindex' @@ -772,7 +785,7 @@ export class Sanitizer implements ISanitizer { 'cols', 'disabled', 'inputmode', - 'name', + ...(this._allowNamedProperties ? ['name'] : []), 'placeholder', 'readonly', 'required', @@ -965,7 +978,7 @@ export class Sanitizer implements ISanitizer { // Since embedded data is no longer deemed to be a threat, validation can be skipped. // See https://github.com/jupyterlab/jupyterlab/issues/5183 allowedSchemesAppliedToAttributes: ['href', 'cite'] - }; + }); } /**
88e24baac551Merge commit from fork
3 files changed · +34 −13
packages/apputils-extension/schema/sanitizer.json+6 −0 modified@@ -19,6 +19,12 @@ "title": "Autolink URL replacement", "description": "Whether to replace URLs with links or not.", "default": true + }, + "allowNamedProperties": { + "type": "boolean", + "title": "Allow named properties", + "description": "Whether to allow untrusted elements to include `name` and `id` attributes. These attributes are stripped by default to prevent DOM clobbering attacks.", + "default": false } }, "type": "object"
packages/apputils-extension/src/index.ts+3 −0 modified@@ -693,12 +693,15 @@ const sanitizer: JupyterFrontEndPlugin<IRenderMime.ISanitizer> = { .composite as Array<string>; const autolink = setting.get('autolink').composite as boolean; + const allowNamedProperties = setting.get('allowNamedProperties') + .composite as boolean; if (allowedSchemes) { sanitizer.setAllowedSchemes(allowedSchemes); } sanitizer.setAutolink(autolink); + sanitizer.setAllowNamedProperties(allowNamedProperties); }; // Wait for the application to be restored and
packages/apputils/src/sanitizer.ts+25 −13 modified@@ -434,6 +434,9 @@ class CssProp { * A class to sanitize HTML strings. */ export class Sanitizer implements IRenderMime.ISanitizer { + constructor() { + this._options = this._generateOptions(); + } /** * Sanitize an HTML string. * @@ -473,9 +476,18 @@ export class Sanitizer implements IRenderMime.ISanitizer { this._autolink = autolink; } - private _autolink: boolean = true; + /** + * Set the whether to allow `name` and `id` attributes. + */ + setAllowNamedProperties(allowNamedProperties: boolean): void { + this._allowNamedProperties = allowNamedProperties; + this._options = this._generateOptions(); + } - private _options: sanitize.IOptions = { + private _autolink: boolean = true; + private _allowNamedProperties: boolean = false; + private _options: sanitize.IOptions; + private _generateOptions = (): sanitize.IOptions => ({ // HTML tags that are allowed to be used. Tags were extracted from Google Caja allowedTags: [ 'a', @@ -590,7 +602,7 @@ export class Sanitizer implements IRenderMime.ISanitizer { 'dir', 'draggable', 'hidden', - 'id', + ...(this._allowNamedProperties ? ['id'] : []), 'inert', 'itemprop', 'itemref', @@ -607,7 +619,7 @@ export class Sanitizer implements IRenderMime.ISanitizer { 'coords', 'href', 'hreflang', - 'name', + ...(this._allowNamedProperties ? ['name'] : []), 'rel', 'shape', 'tabindex', @@ -641,7 +653,7 @@ export class Sanitizer implements IRenderMime.ISanitizer { 'data-commandlinker-args', 'data-commandlinker-command', 'disabled', - 'name', + ...(this._allowNamedProperties ? ['name'] : []), 'tabindex', 'type', 'value' @@ -672,7 +684,7 @@ export class Sanitizer implements IRenderMime.ISanitizer { 'autocomplete', 'enctype', 'method', - 'name', + ...(this._allowNamedProperties ? ['name'] : []), 'novalidate' ], h1: ['align'], @@ -697,7 +709,7 @@ export class Sanitizer implements IRenderMime.ISanitizer { 'height', 'hspace', 'ismap', - 'name', + ...(this._allowNamedProperties ? ['name'] : []), 'src', 'usemap', 'vspace', @@ -718,7 +730,7 @@ export class Sanitizer implements IRenderMime.ISanitizer { 'maxlength', 'min', 'multiple', - 'name', + ...(this._allowNamedProperties ? ['name'] : []), 'placeholder', 'readonly', 'required', @@ -734,13 +746,13 @@ export class Sanitizer implements IRenderMime.ISanitizer { label: ['accesskey', 'for'], legend: ['accesskey', 'align'], li: ['type', 'value'], - map: ['name'], + map: this._allowNamedProperties ? ['name'] : [], menu: ['compact', 'label', 'type'], meter: ['high', 'low', 'max', 'min', 'value'], ol: ['compact', 'reversed', 'start', 'type'], optgroup: ['disabled', 'label'], option: ['disabled', 'label', 'selected', 'value'], - output: ['for', 'name'], + output: ['for', ...(this._allowNamedProperties ? ['name'] : [])], p: ['align'], pre: ['width'], progress: ['max', 'min', 'value'], @@ -749,7 +761,7 @@ export class Sanitizer implements IRenderMime.ISanitizer { 'autocomplete', 'disabled', 'multiple', - 'name', + ...(this._allowNamedProperties ? ['name'] : []), 'required', 'size', 'tabindex' @@ -789,7 +801,7 @@ export class Sanitizer implements IRenderMime.ISanitizer { 'cols', 'disabled', 'inputmode', - 'name', + ...(this._allowNamedProperties ? ['name'] : []), 'placeholder', 'readonly', 'required', @@ -982,5 +994,5 @@ export class Sanitizer implements IRenderMime.ISanitizer { // Since embedded data is no longer deemed to be a threat, validation can be skipped. // See https://github.com/jupyterlab/jupyterlab/issues/5183 allowedSchemesAppliedToAttributes: ['href', 'cite'] - }; + }); }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-9q39-rmj3-p4r2ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-43805ghsaADVISORY
- github.com/jupyterlab/jupyterlab/commit/06ad9de836f155add7d3d651ef936cc4c5ea8093ghsaWEB
- github.com/jupyterlab/jupyterlab/commit/88e24baac551196f9cb3de16bd060a7ab1597674ghsaWEB
- github.com/jupyterlab/jupyterlab/security/advisories/GHSA-9q39-rmj3-p4r2ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.