VYPR
Moderate severityNVD Advisory· Published Jan 19, 2024· Updated May 30, 2025

Stored cross site scripting in Markdown Preview in JupyterLab

CVE-2024-22420

Description

JupyterLab is an extensible environment for interactive and reproducible computing, based on the Jupyter Notebook and Architecture. This vulnerability depends on user interaction by opening a malicious 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 version 4.0.11 has been patched. Users are advised to upgrade. Users unable to upgrade should disable the table of contents extension.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A cross-site scripting (XSS) vulnerability in JupyterLab's Markdown preview lets attackers execute arbitrary requests as the victim by tricking them into opening a crafted .md file.

Vulnerability

Overview

CVE-2024-22420 is a security vulnerability in JupyterLab's Markdown preview feature, stemming from insufficient sanitization of HTML content rendered by the Markdown viewer. The bug allows a malicious Markdown file to inject arbitrary JavaScript when the victim opens it using the JupyterLab file preview functionality. This issue is rooted in how the Markdown table of contents extension renders untrusted content without proper sanitization [1][2].

Exploitation

Path

Exploitation requires user interaction: the attacker must convince the victim to open a specially crafted Markdown file in JupyterLab's preview pane. The vulnerability does not require any authentication bypass or special network position beyond the ability to deliver a .md file to the target (e.g., via a shared notebook, repository, or email link). Once the file is previewed, the injected script executes within the context of the JupyterLab application [3].

Impact

A successful attack enables the attacker to perform arbitrary requests as the victim, including accessing any data the victim has access to within the Jupyter environment. This could lead to unauthorized data exfiltration, modification of notebooks, or further compromise of the JupyterLab session [3].

Mitigation

The vulnerability is patched in JupyterLab version 4.0.11. Users are strongly advised to upgrade. For those unable to upgrade immediately, disabling the table of contents extension serves as a temporary workaround to block the attack vector [3].

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.

PackageAffected versionsPatched versions
jupyterlabPyPI
>= 4.0.0, < 4.0.114.0.11
notebookPyPI
>= 7.0.0, < 7.0.77.0.7

Affected products

6

Patches

2
dda0033cd494

Merge pull request from GHSA-4m77-cmpx-vjc4

https://github.com/jupyterlab/jupyterlabMichał KrassowskiJan 19, 2024via ghsa
11 files changed · +85 26
  • packages/markdownviewer-extension/src/index.ts+12 4 modified
    @@ -10,7 +10,7 @@ import {
       JupyterFrontEnd,
       JupyterFrontEndPlugin
     } from '@jupyterlab/application';
    -import { WidgetTracker } from '@jupyterlab/apputils';
    +import { ISanitizer, WidgetTracker } from '@jupyterlab/apputils';
     import { PathExt } from '@jupyterlab/coreutils';
     import {
       IMarkdownViewerTracker,
    @@ -20,6 +20,7 @@ import {
       MarkdownViewerTableOfContentsFactory
     } from '@jupyterlab/markdownviewer';
     import {
    +  IRenderMime,
       IRenderMimeRegistry,
       markdownRendererFactory
     } from '@jupyterlab/rendermime';
    @@ -49,7 +50,12 @@ const plugin: JupyterFrontEndPlugin<IMarkdownViewerTracker> = {
       description: 'Adds markdown file viewer and provides its tracker.',
       provides: IMarkdownViewerTracker,
       requires: [IRenderMimeRegistry, ITranslator],
    -  optional: [ILayoutRestorer, ISettingRegistry, ITableOfContentsRegistry],
    +  optional: [
    +    ILayoutRestorer,
    +    ISettingRegistry,
    +    ITableOfContentsRegistry,
    +    ISanitizer
    +  ],
       autoStart: true
     };
     
    @@ -62,7 +68,8 @@ function activate(
       translator: ITranslator,
       restorer: ILayoutRestorer | null,
       settingRegistry: ISettingRegistry | null,
    -  tocRegistry: ITableOfContentsRegistry | null
    +  tocRegistry: ITableOfContentsRegistry | null,
    +  sanitizer: IRenderMime.ISanitizer | null
     ): IMarkdownViewerTracker {
       const trans = translator.load('jupyterlab');
       const { commands, docRegistry } = app;
    @@ -182,7 +189,8 @@ function activate(
         tocRegistry.add(
           new MarkdownViewerTableOfContentsFactory(
             tracker,
    -        rendermime.markdownParser
    +        rendermime.markdownParser,
    +        sanitizer ?? rendermime.sanitizer
           )
         );
       }
    
  • packages/markdownviewer/src/toc.ts+6 4 modified
    @@ -2,7 +2,7 @@
     // Distributed under the terms of the Modified BSD License.
     
     import { IWidgetTracker } from '@jupyterlab/apputils';
    -import { IMarkdownParser } from '@jupyterlab/rendermime';
    +import { IMarkdownParser, IRenderMime } from '@jupyterlab/rendermime';
     import {
       TableOfContents,
       TableOfContentsFactory,
    @@ -96,7 +96,8 @@ export class MarkdownViewerTableOfContentsFactory extends TableOfContentsFactory
        */
       constructor(
         tracker: IWidgetTracker<MarkdownDocument>,
    -    protected parser: IMarkdownParser | null
    +    protected parser: IMarkdownParser | null,
    +    protected sanitizer: IRenderMime.ISanitizer
       ) {
         super(tracker);
       }
    @@ -165,13 +166,14 @@ export class MarkdownViewerTableOfContentsFactory extends TableOfContentsFactory
             const elementId = await TableOfContentsUtils.Markdown.getHeadingId(
               this.parser!,
               heading.raw,
    -          heading.level
    +          heading.level,
    +          this.sanitizer
             );
     
             if (!elementId) {
               return;
             }
    -        const selector = `h${heading.level}[id="${elementId}"]`;
    +        const selector = `h${heading.level}[id="${CSS.escape(elementId)}"]`;
     
             headingToElement.set(
               heading,
    
  • packages/notebook/src/toc.ts+10 4 modified
    @@ -588,10 +588,14 @@ export class NotebookToCFactory extends TableOfContentsFactory<NotebookPanel> {
     
         const findHeadingElement = (cell: Cell): void => {
           model.getCellHeadings(cell).forEach(async heading => {
    -        const elementId = await getIdForHeading(heading, this.parser!);
    +        const elementId = await getIdForHeading(
    +          heading,
    +          this.parser!,
    +          this.sanitizer
    +        );
     
             const selector = elementId
    -          ? `h${heading.level}[id="${elementId}"]`
    +          ? `h${heading.level}[id="${CSS.escape(elementId)}"]`
               : `h${heading.level}`;
     
             if (heading.outputIndex !== undefined) {
    @@ -710,15 +714,17 @@ export class NotebookToCFactory extends TableOfContentsFactory<NotebookPanel> {
      */
     export async function getIdForHeading(
       heading: INotebookHeading,
    -  parser: IRenderMime.IMarkdownParser
    +  parser: IRenderMime.IMarkdownParser,
    +  sanitizer: IRenderMime.ISanitizer
     ) {
       let elementId: string | null = null;
       if (heading.type === Cell.HeadingType.Markdown) {
         elementId = await TableOfContentsUtils.Markdown.getHeadingId(
           parser,
           // Type from TableOfContentsUtils.Markdown.IMarkdownHeading
           (heading as any).raw,
    -      heading.level
    +      heading.level,
    +      sanitizer
         );
       } else if (heading.type === Cell.HeadingType.HTML) {
         // Type from TableOfContentsUtils.IHTMLHeading
    
  • packages/notebook/src/widget.ts+3 2 modified
    @@ -2191,14 +2191,15 @@ export class Notebook extends StaticNotebook {
                   id = await TableOfContentsUtils.Markdown.getHeadingId(
                     this.rendermime.markdownParser!,
                     mdHeading.raw,
    -                mdHeading.level
    +                mdHeading.level,
    +                this.rendermime.sanitizer
                   );
                 }
                 break;
             }
             if (id === queryId) {
               const element = this.node.querySelector(
    -            `h${heading.level}[id="${id}"]`
    +            `h${heading.level}[id="${CSS.escape(id)}"]`
               ) as HTMLElement;
     
               return {
    
  • packages/rendermime-interfaces/src/index.ts+2 2 modified
    @@ -486,10 +486,10 @@ export namespace IRenderMime {
        */
       export interface IMarkdownParser {
         /**
    -     * Render a markdown source.
    +     * Render a markdown source into unsanitized HTML.
          *
          * @param source - The string to render.
    -     * @returns - A promise of the string.
    +     * @returns - A promise of the string containing HTML which may require sanitization.
          */
         render(source: string): Promise<string>;
       }
    
  • packages/toc/package.json+1 0 modified
    @@ -46,6 +46,7 @@
         "@jupyterlab/docregistry": "^4.0.10",
         "@jupyterlab/observables": "^5.0.10",
         "@jupyterlab/rendermime": "^4.0.10",
    +    "@jupyterlab/rendermime-interfaces": "^3.8.10",
         "@jupyterlab/translation": "^4.0.10",
         "@jupyterlab/ui-components": "^4.0.10",
         "@lumino/coreutils": "^2.1.2",
    
  • packages/toc/src/utils/markdown.ts+20 10 modified
    @@ -1,7 +1,9 @@
     // Copyright (c) Jupyter Development Team.
     // Distributed under the terms of the Modified BSD License.
     
    +import { Sanitizer } from '@jupyterlab/apputils';
     import { IMarkdownParser, renderMarkdown } from '@jupyterlab/rendermime';
    +import { IRenderMime } from '@jupyterlab/rendermime-interfaces';
     import { TableOfContents } from '../tokens';
     
     /**
    @@ -24,27 +26,35 @@ export interface IMarkdownHeading extends TableOfContents.IHeading {
      *
      * @param raw Raw markdown heading
      * @param level Heading level
    + * @param sanitizer HTML sanitizer
      */
     export async function getHeadingId(
    -  parser: IMarkdownParser,
    +  markdownParser: IMarkdownParser,
       raw: string,
    -  level: number
    +  level: number,
    +  sanitizer?: IRenderMime.ISanitizer
     ): Promise<string | null> {
       try {
    -    const innerHTML = await parser.render(raw);
    +    const host = document.createElement('div');
     
    -    if (!innerHTML) {
    -      return null;
    -    }
    +    await renderMarkdown({
    +      markdownParser,
    +      host,
    +      source: raw,
    +      trusted: false,
    +      sanitizer: sanitizer ?? new Sanitizer(),
    +      shouldTypeset: false,
    +      resolver: null,
    +      linkHandler: null,
    +      latexTypesetter: null
    +    });
     
    -    const container = document.createElement('div');
    -    container.innerHTML = innerHTML;
    -    const header = container.querySelector(`h${level}`);
    +    const header = host.querySelector(`h${level}`);
         if (!header) {
           return null;
         }
     
    -    return renderMarkdown.createHeaderId(header);
    +    return header.id;
       } catch (reason) {
         console.error('Failed to parse a heading.', reason);
       }
    
  • packages/toc/test/markdown.spec.ts+24 0 modified
    @@ -2,9 +2,33 @@
     // Distributed under the terms of the Modified BSD License.
     
     import { TableOfContentsUtils } from '@jupyterlab/toc';
    +import { Sanitizer } from '@jupyterlab/apputils';
    +import { createMarkdownParser } from '@jupyterlab/markedparser-extension';
    +import { IMarkdownParser } from '@jupyterlab/rendermime';
    +import {
    +  EditorLanguageRegistry,
    +  IEditorLanguageRegistry
    +} from '@jupyterlab/codemirror';
     
     describe('TableOfContentsUtils', () => {
       describe('Markdown', () => {
    +    describe('#getHeadingId', () => {
    +      const languages: IEditorLanguageRegistry = new EditorLanguageRegistry();
    +      const parser: IMarkdownParser = createMarkdownParser(languages);
    +      const sanitizer = new Sanitizer();
    +      it.each<[string, string]>([
    +        ['# Title', 'Title'],
    +        [`# test'"></title><img>test {#'"><img>}`, `test'\">test-{#'\">}`]
    +      ])('should derive ID from markdown', async (markdown, expectedId) => {
    +        const headingId = await TableOfContentsUtils.Markdown.getHeadingId(
    +          parser,
    +          markdown,
    +          1,
    +          sanitizer
    +        );
    +        expect(headingId).toEqual(expectedId);
    +      });
    +    });
         describe('#getHeadings', () => {
           it.each<[string, TableOfContentsUtils.Markdown.IMarkdownHeading[]]>([
             [
    
  • packages/toc/tsconfig.json+3 0 modified
    @@ -26,6 +26,9 @@
         },
         {
           "path": "../ui-components"
    +    },
    +    {
    +      "path": "../rendermime-interfaces"
         }
       ]
     }
    
  • packages/toc/tsconfig.test.json+3 0 modified
    @@ -26,6 +26,9 @@
         {
           "path": "../ui-components"
         },
    +    {
    +      "path": "../rendermime-interfaces"
    +    },
         {
           "path": "."
         },
    
  • yarn.lock+1 0 modified
    @@ -4757,6 +4757,7 @@ __metadata:
         "@jupyterlab/docregistry": ^4.0.10
         "@jupyterlab/observables": ^5.0.10
         "@jupyterlab/rendermime": ^4.0.10
    +    "@jupyterlab/rendermime-interfaces": ^3.8.10
         "@jupyterlab/testing": ^4.0.10
         "@jupyterlab/translation": ^4.0.10
         "@jupyterlab/ui-components": ^4.0.10
    
e1b3aabab603

Merge pull request from GHSA-4m77-cmpx-vjc4

https://github.com/jupyterlab/jupyterlabMichał KrassowskiJan 19, 2024via ghsa
11 files changed · +85 26
  • packages/markdownviewer-extension/src/index.ts+12 4 modified
    @@ -10,7 +10,7 @@ import {
       JupyterFrontEnd,
       JupyterFrontEndPlugin
     } from '@jupyterlab/application';
    -import { WidgetTracker } from '@jupyterlab/apputils';
    +import { ISanitizer, WidgetTracker } from '@jupyterlab/apputils';
     import { PathExt } from '@jupyterlab/coreutils';
     import {
       IMarkdownViewerTracker,
    @@ -20,6 +20,7 @@ import {
       MarkdownViewerTableOfContentsFactory
     } from '@jupyterlab/markdownviewer';
     import {
    +  IRenderMime,
       IRenderMimeRegistry,
       markdownRendererFactory
     } from '@jupyterlab/rendermime';
    @@ -49,7 +50,12 @@ const plugin: JupyterFrontEndPlugin<IMarkdownViewerTracker> = {
       description: 'Adds markdown file viewer and provides its tracker.',
       provides: IMarkdownViewerTracker,
       requires: [IRenderMimeRegistry, ITranslator],
    -  optional: [ILayoutRestorer, ISettingRegistry, ITableOfContentsRegistry],
    +  optional: [
    +    ILayoutRestorer,
    +    ISettingRegistry,
    +    ITableOfContentsRegistry,
    +    ISanitizer
    +  ],
       autoStart: true
     };
     
    @@ -62,7 +68,8 @@ function activate(
       translator: ITranslator,
       restorer: ILayoutRestorer | null,
       settingRegistry: ISettingRegistry | null,
    -  tocRegistry: ITableOfContentsRegistry | null
    +  tocRegistry: ITableOfContentsRegistry | null,
    +  sanitizer: IRenderMime.ISanitizer | null
     ): IMarkdownViewerTracker {
       const trans = translator.load('jupyterlab');
       const { commands, docRegistry } = app;
    @@ -182,7 +189,8 @@ function activate(
         tocRegistry.add(
           new MarkdownViewerTableOfContentsFactory(
             tracker,
    -        rendermime.markdownParser
    +        rendermime.markdownParser,
    +        sanitizer ?? rendermime.sanitizer
           )
         );
       }
    
  • packages/markdownviewer/src/toc.ts+6 4 modified
    @@ -2,7 +2,7 @@
     // Distributed under the terms of the Modified BSD License.
     
     import { IWidgetTracker } from '@jupyterlab/apputils';
    -import { IMarkdownParser } from '@jupyterlab/rendermime';
    +import { IMarkdownParser, IRenderMime } from '@jupyterlab/rendermime';
     import {
       TableOfContents,
       TableOfContentsFactory,
    @@ -96,7 +96,8 @@ export class MarkdownViewerTableOfContentsFactory extends TableOfContentsFactory
        */
       constructor(
         tracker: IWidgetTracker<MarkdownDocument>,
    -    protected parser: IMarkdownParser | null
    +    protected parser: IMarkdownParser | null,
    +    protected sanitizer: IRenderMime.ISanitizer
       ) {
         super(tracker);
       }
    @@ -165,13 +166,14 @@ export class MarkdownViewerTableOfContentsFactory extends TableOfContentsFactory
             const elementId = await TableOfContentsUtils.Markdown.getHeadingId(
               this.parser!,
               heading.raw,
    -          heading.level
    +          heading.level,
    +          this.sanitizer
             );
     
             if (!elementId) {
               return;
             }
    -        const selector = `h${heading.level}[id="${elementId}"]`;
    +        const selector = `h${heading.level}[id="${CSS.escape(elementId)}"]`;
     
             headingToElement.set(
               heading,
    
  • packages/notebook/src/toc.ts+10 4 modified
    @@ -649,10 +649,14 @@ export class NotebookToCFactory extends TableOfContentsFactory<NotebookPanel> {
     
         const findHeadingElement = (cell: Cell): void => {
           model.getCellHeadings(cell).forEach(async heading => {
    -        const elementId = await getIdForHeading(heading, this.parser!);
    +        const elementId = await getIdForHeading(
    +          heading,
    +          this.parser!,
    +          this.sanitizer
    +        );
     
             const selector = elementId
    -          ? `h${heading.level}[id="${elementId}"]`
    +          ? `h${heading.level}[id="${CSS.escape(elementId)}"]`
               : `h${heading.level}`;
     
             if (heading.outputIndex !== undefined) {
    @@ -771,15 +775,17 @@ export class NotebookToCFactory extends TableOfContentsFactory<NotebookPanel> {
      */
     export async function getIdForHeading(
       heading: INotebookHeading,
    -  parser: IRenderMime.IMarkdownParser
    +  parser: IRenderMime.IMarkdownParser,
    +  sanitizer: IRenderMime.ISanitizer
     ) {
       let elementId: string | null = null;
       if (heading.type === Cell.HeadingType.Markdown) {
         elementId = await TableOfContentsUtils.Markdown.getHeadingId(
           parser,
           // Type from TableOfContentsUtils.Markdown.IMarkdownHeading
           (heading as any).raw,
    -      heading.level
    +      heading.level,
    +      sanitizer
         );
       } else if (heading.type === Cell.HeadingType.HTML) {
         // Type from TableOfContentsUtils.IHTMLHeading
    
  • packages/notebook/src/widget.ts+3 2 modified
    @@ -2305,14 +2305,15 @@ export class Notebook extends StaticNotebook {
                   id = await TableOfContentsUtils.Markdown.getHeadingId(
                     this.rendermime.markdownParser!,
                     mdHeading.raw,
    -                mdHeading.level
    +                mdHeading.level,
    +                this.rendermime.sanitizer
                   );
                 }
                 break;
             }
             if (id === queryId) {
               const element = this.node.querySelector(
    -            `h${heading.level}[id="${id}"]`
    +            `h${heading.level}[id="${CSS.escape(id)}"]`
               ) as HTMLElement;
     
               return {
    
  • packages/rendermime-interfaces/src/index.ts+2 2 modified
    @@ -525,10 +525,10 @@ export namespace IRenderMime {
        */
       export interface IMarkdownParser {
         /**
    -     * Render a markdown source.
    +     * Render a markdown source into unsanitized HTML.
          *
          * @param source - The string to render.
    -     * @returns - A promise of the string.
    +     * @returns - A promise of the string containing HTML which may require sanitization.
          */
         render(source: string): Promise<string>;
       }
    
  • packages/toc/package.json+1 0 modified
    @@ -46,6 +46,7 @@
         "@jupyterlab/docregistry": "^4.1.0-beta.1",
         "@jupyterlab/observables": "^5.1.0-beta.1",
         "@jupyterlab/rendermime": "^4.1.0-beta.1",
    +    "@jupyterlab/rendermime-interfaces": "^3.9.0-beta.1",
         "@jupyterlab/translation": "^4.1.0-beta.1",
         "@jupyterlab/ui-components": "^4.1.0-beta.1",
         "@lumino/coreutils": "^2.1.2",
    
  • packages/toc/src/utils/markdown.ts+20 10 modified
    @@ -1,7 +1,9 @@
     // Copyright (c) Jupyter Development Team.
     // Distributed under the terms of the Modified BSD License.
     
    +import { Sanitizer } from '@jupyterlab/apputils';
     import { IMarkdownParser, renderMarkdown } from '@jupyterlab/rendermime';
    +import { IRenderMime } from '@jupyterlab/rendermime-interfaces';
     import { TableOfContents } from '../tokens';
     
     /**
    @@ -24,27 +26,35 @@ export interface IMarkdownHeading extends TableOfContents.IHeading {
      *
      * @param raw Raw markdown heading
      * @param level Heading level
    + * @param sanitizer HTML sanitizer
      */
     export async function getHeadingId(
    -  parser: IMarkdownParser,
    +  markdownParser: IMarkdownParser,
       raw: string,
    -  level: number
    +  level: number,
    +  sanitizer?: IRenderMime.ISanitizer
     ): Promise<string | null> {
       try {
    -    const innerHTML = await parser.render(raw);
    +    const host = document.createElement('div');
     
    -    if (!innerHTML) {
    -      return null;
    -    }
    +    await renderMarkdown({
    +      markdownParser,
    +      host,
    +      source: raw,
    +      trusted: false,
    +      sanitizer: sanitizer ?? new Sanitizer(),
    +      shouldTypeset: false,
    +      resolver: null,
    +      linkHandler: null,
    +      latexTypesetter: null
    +    });
     
    -    const container = document.createElement('div');
    -    container.innerHTML = innerHTML;
    -    const header = container.querySelector(`h${level}`);
    +    const header = host.querySelector(`h${level}`);
         if (!header) {
           return null;
         }
     
    -    return renderMarkdown.createHeaderId(header);
    +    return header.id;
       } catch (reason) {
         console.error('Failed to parse a heading.', reason);
       }
    
  • packages/toc/test/markdown.spec.ts+24 0 modified
    @@ -2,9 +2,33 @@
     // Distributed under the terms of the Modified BSD License.
     
     import { TableOfContentsUtils } from '@jupyterlab/toc';
    +import { Sanitizer } from '@jupyterlab/apputils';
    +import { createMarkdownParser } from '@jupyterlab/markedparser-extension';
    +import { IMarkdownParser } from '@jupyterlab/rendermime';
    +import {
    +  EditorLanguageRegistry,
    +  IEditorLanguageRegistry
    +} from '@jupyterlab/codemirror';
     
     describe('TableOfContentsUtils', () => {
       describe('Markdown', () => {
    +    describe('#getHeadingId', () => {
    +      const languages: IEditorLanguageRegistry = new EditorLanguageRegistry();
    +      const parser: IMarkdownParser = createMarkdownParser(languages);
    +      const sanitizer = new Sanitizer();
    +      it.each<[string, string]>([
    +        ['# Title', 'Title'],
    +        [`# test'"></title><img>test {#'"><img>}`, `test'\">test-{#'\">}`]
    +      ])('should derive ID from markdown', async (markdown, expectedId) => {
    +        const headingId = await TableOfContentsUtils.Markdown.getHeadingId(
    +          parser,
    +          markdown,
    +          1,
    +          sanitizer
    +        );
    +        expect(headingId).toEqual(expectedId);
    +      });
    +    });
         describe('#getHeadings', () => {
           it.each<[string, TableOfContentsUtils.Markdown.IMarkdownHeading[]]>([
             [
    
  • packages/toc/tsconfig.json+3 0 modified
    @@ -26,6 +26,9 @@
         },
         {
           "path": "../ui-components"
    +    },
    +    {
    +      "path": "../rendermime-interfaces"
         }
       ]
     }
    
  • packages/toc/tsconfig.test.json+3 0 modified
    @@ -26,6 +26,9 @@
         {
           "path": "../ui-components"
         },
    +    {
    +      "path": "../rendermime-interfaces"
    +    },
         {
           "path": "."
         },
    
  • yarn.lock+1 0 modified
    @@ -4908,6 +4908,7 @@ __metadata:
         "@jupyterlab/docregistry": ^4.1.0-beta.1
         "@jupyterlab/observables": ^5.1.0-beta.1
         "@jupyterlab/rendermime": ^4.1.0-beta.1
    +    "@jupyterlab/rendermime-interfaces": ^3.9.0-beta.1
         "@jupyterlab/testing": ^4.1.0-beta.1
         "@jupyterlab/translation": ^4.1.0-beta.1
         "@jupyterlab/ui-components": ^4.1.0-beta.1
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.