# Visual Studio Code Jupyter Notebook RCE

XSS in VSCode's Jupyter Notebook markdown renderer, chained with vscode-file path traversal and sandbox allow-same-origin to reach nodeIntegration and execute code.

- Payout: Research
- Severity: high
- Program: Visual Studio Code
- Credited: Doyensec
- Disclosed: 2022-10-26
- CVE: CVE-2021-26437
- Write-up quality: 8/10
- Original: https://blog.doyensec.com/2022/10/27/jupytervscode.html
- Library page: https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2022-10-27-jupytervscode-html
- Tags: domain:desktop impact:rce kind:research platform:research sev:high tech:electron technique:chained technique:source-review vuln:path-traversal vuln:rce vuln:xss

**Target:** VSCode Jupyter Notebook (.ipynb) markdown output renderer / Electron webview
**Root cause:** Jupyter Notebook markdown output is rendered as HTML without sanitization, yielding XSS inside a sandboxed iframe that has allow-same-origin and allow-scripts. Combined with a vscode-file path traversal, attacker HTML can be loaded from the app folder and reach the top window with nodeIntegration:on.
**Steps:**
1. Craft .ipynb with a display_data output whose text/markdown contains `<img src=x onerror='...'>` to trigger XSS.
2. Observe the XSS runs in a nested iframe with `allow-scripts allow-same-origin allow-forms allow-pointer-lock allow-downloads`.
3. Use `window.top.postMessage({target:...,channel:'do-reload'},'*')` and hook `window.top.frames[0].onmessage` to leak the repo path from the `<base href>` in the reply.
4. Rewrite the leaked `https://file%2B.vscode-resource.vscode-webview.net` prefix to `vscode-file://vscode-app` + app path + `..%2F` traversal to reach the attacker-controlled `poc.html` in the repo.
5. Set `location.href` to the traversed URL so the iframe loads attacker HTML from the app folder, satisfying same-origin with the top window.
6. In poc.html call `top.require('child_process').exec('open /System/Applications/Calculator.app')` to execute code.
7. Encode the JS payload using backticks instead of quotes to keep the .ipynb JSON valid.
**How found:** Read Justin Steven's prior XSS writeup, then used ElectroNG to enumerate BrowserWindow configs and noticed a single BrowserWindow with nodeIntegration:on plus a sandboxed iframe with allow-same-origin. Recalled TheGrandPew/s1r1us Black Hat USA 2022 path traversal in vscode-file (CVE-2021-43908) to load attacker HTML from the app folder, and after trial-and-error found the img onload trick to get a reference to the triggering element and leak the repo path via postMessage.
**Exploitation:** Attacker ships a malicious repository containing a crafted .ipynb and poc.html. Victim opens the notebook in VSCode; the markdown XSS fires onload, leaks the repo path via postMessage, redirects the iframe to a vscode-file:// URL that path-traverses to poc.html in the repo, and that page (same-origin with the top window due to allow-same-origin) calls top.require to spawn a process. Workspace Trust is bypassed because the Jupyter extension opts out, so no user interaction beyond opening the file.
**Escalation:** Chained XSS -> postMessage path leak -> vscode-file path traversal -> same-origin iframe -> nodeIntegration require() for full RCE.
**Insight:** When an XSS lands in a sandboxed iframe, check the sandbox flags: allow-same-origin plus allow-scripts means you can reach the top window if you can get your content served from the same origin. Look for custom protocol handlers (vscode-file, app://, etc.) that accept encoded traversal (`..%2F`) to relocate attacker content into the trusted origin, and use postMessage replies (e.g. `<base href>` leaks) to discover filesystem paths.
