Skip to content

fix(vscode): sanitize chat webview markdown and block remote images - #1927

Open
Mahdi-Hasan wants to merge 1 commit into
Nano-Collective:mainfrom
Mahdi-Hasan:fix/1727-sanitize-webview-markdown
Open

Mahdi-Hasan wants to merge 1 commit into
Nano-Collective:mainfrom
Mahdi-Hasan:fix/1727-sanitize-webview-markdown

Conversation

@Mahdi-Hasan

@Mahdi-Hasan Mahdi-Hasan commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Description

Sanitizes every markdown render in the VS Code chat webview and stops remote image loads. Fixes #1727.

  • There are 5 places where marked.parse() output is written to innerHTML, not 3: user message, first agent chunk, throttled re-render, turn-end flush, and thoughts. All 5 now go through one setMarkdown() helper, which renders via the new media/markdown-utils.js.
  • markdown-utils.js runs marked's output through DOMPurify 3.4.17. It ships as media/purify.min.js the same way marked.min.js does, byte-identical to the npm build.
  • Sanitizer policy:
    • HTML only (no SVG or MathML).
    • No <style>, style="" or <form>.
    • id and name get a user-content- prefix.
    • Every class is dropped except a code block's language-*. Without this, a reply could use the panel's Tailwind classes (e.g. fixed inset-0) to cover the UI.
  • If DOMPurify is missing or reports no DOM support, the panel shows plain text, never raw HTML.
  • The CSP img-src no longer allows https:. Pasted and uploaded images are data: URIs, and nothing else in the panel loads remote images.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Changeset

  • Added a changeset (pnpm changeset) describing this change for the changelog

Testing

Automated Tests

  • New features include passing tests in .spec.ts/tsx files

  • All existing tests pass (pnpm test:all completes successfully)

  • Tests cover both success and error scenarios

  • plugins/vscode/src/markdown-utils.spec.ts (14 tests) loads the shipped marked, DOMPurify and helper into jsdom. It checks what gets removed, what still renders, the plain-text fallbacks, and the CSP. jsdom is a new devDependency of the extension only, and it isn't packaged because vsce runs with --no-dependencies.

  • source/vscode/chat-panel-markdown.spec.ts (5 tests) drives the panel harness and checks that all 5 places are sanitized.

  • Format, types (root and vscode), lint, knip and audit pass.

  • I developed this on Windows, where pnpm test:ava has many failures on main too (POSIX paths, spawn EINVAL). This branch adds no new failures. CI on Linux should settle it.

Manual Testing

  • Tested with Ollama
  • Tested with OpenRouter
  • Tested with OpenAI-compatible API
  • Tested MCP integration (if applicable)

Checklist

  • If this was for an open issue, I was assigned to it (I commented on the issue to claim it; happy to adjust if someone else was already on it)
  • Code follows project style guidelines
  • Self-review completed
  • Documentation updated (if needed)
  • No breaking changes (or clearly documented)
  • Appropriate logging added using structured logging (see CONTRIBUTING.md)

@github-actions github-actions Bot added the area:vscode VS Code extension and host integration label Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

nc-review could not produce a verdict this run (agent exit 1). This is a problem with the review agent, not with your pull request. A maintainer can retry with /re-review.

@Mahdi-Hasan
Mahdi-Hasan force-pushed the fix/1727-sanitize-webview-markdown branch from d0be1a3 to 3a4c462 Compare November 2, 2026 12:24
The chat panel put marked.parse() output for replies, thoughts and
replayed user messages straight into innerHTML. Raw HTML in a reply
could add <style> rules, inline styles or the panel's own Tailwind
classes and restyle or cover the webview. The CSP also allowed any
https: image, so a reply could make the editor fetch a tracking pixel.

All five marked sinks now go through setMarkdown(), which renders via
the new media/markdown-utils.js. It runs marked output through a
vendored DOMPurify 3.4.17 build (media/purify.min.js, loaded the same
way as marked.min.js) with the HTML profile only, no <style>, style="",
or <form>, prefixed ids, and no class other than a code block's
language-*. If DOMPurify is missing or reports no DOM support, the
panel shows the text instead of unsanitized HTML.

img-src drops https:. Uploaded and pasted images are data: URIs and
nothing else in the panel loads remote images.

markdown-utils.spec.ts runs the shipped marked, DOMPurify and helper
in jsdom (new devDependency of the extension) and checks the policy and
the CSP. chat-panel-markdown.spec.ts drives the panel harness to check
every sink is sanitized and that it fails closed.

Fixes Nano-Collective#1727

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Mahdi-Hasan
Mahdi-Hasan force-pushed the fix/1727-sanitize-webview-markdown branch from 3a4c462 to b3ebbd0 Compare November 4, 2026 06:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:vscode VS Code extension and host integration

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Chat webview renders Markdown into innerHTML without sanitization, permitting layout injection and external image tracking

1 participant