Skip to content

Browser build defaults to no-op HTML sanitizer — Stored XSS via crafted .ipynb files #58

Description

@ya3raj

Summary

notebookjs v0.8.3 defaults to an identity (no-op) HTML sanitizer when DOMPurify is not pre-loaded by the host application. The browser build (notebook.min.js) does not bundle DOMPurify. Any application rendering untrusted Jupyter notebooks in a browser without separately loading DOMPurify is vulnerable to Stored Cross-Site Scripting (XSS).

Root Cause

notebook.js line 67:

sanitizer: getSanitizer() || ident

getSanitizer() returns undefined when DOMPurify is not present as a global variable in the browser. The fallback function ident returns its input unchanged. All HTML output passes through nb.sanitizer(joinText(html)) — which becomes a no-op, directly setting el.innerHTML to attacker-controlled notebook content.

The README documents DOMPurify as optional:

"The browser-based version does not ship with those libraries, so you must <script>-include or require them before initializing notebook.js."

This creates a security vulnerability in any integration where the developer either:

  • Omits the DOMPurify script tag
  • Is unaware that DOMPurify is required for security (the README frames it as a Markdown/ANSI dependency, not a security requirement)

Steps to Reproduce

  1. Install notebookjs: npm install notebookjs
  2. Serve notebook.min.js in a browser page
  3. Render this .ipynb file:
{"cells":[{"cell_type":"markdown","metadata":{},"source":["<img src=x onerror=alert('NPM-NOTEBOOKJS-XSS')>"]}],"metadata":{},"nbformat":4,"nbformat_minor":2}
  1. The onerror handler executes — stored XSS confirmed.

PoC Files

<!DOCTYPE html>
<html><body><div id="output"></div>
<script src="notebook.min.js"></script>
<script>
fetch('xss.ipynb').then(r => r.json()).then(ipynb => {
  document.getElementById('output').appendChild(nb.parse(ipynb).render());
});
</script>
</body></html>

Impact

An attacker uploads a malicious .ipynb file containing an XSS payload to any platform that renders notebooks using notebookjs without DOMPurify. When a victim views the notebook, the payload executes in their browser session — enabling session hijacking, credential theft, and full account takeover.

Affected Versions

All versions through 0.8.3 (latest). All known forks (@openbayes/notebookjs, @determined-ai/notebook-render) are also affected.

Fix

Bundle dompurify with the browser build so the sanitizer is always functional. At minimum, add a console warning when the sanitizer falls back to ident:

if (!lib) {
    console.warn('notebookjs: DOMPurify not loaded — HTML output will not be sanitized. XSS risk.');
}
return lib ? lib.sanitize : function(x) { return x; };

Or throw an error if DOMPurify is missing, forcing integrators to include it.


Affected package

notebookjs (npm) — all versions ≤ 0.8.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions