Repository navigation
Renderer: sanitize raw HTML in every Markdown Streamdown renders - #1177
Merged
Merged
Conversation
Streamdown parses raw HTML in Markdown (rehype-raw) and only hardens URLs. A Spaces message, a model's reply or a meeting summary could therefore carry <iframe srcdoc="<script>…">, and its script ran in the renderer's own origin with window.ipc in reach. Reproduced against a local Harbor: a message from another member ran script on render. <img onerror> and javascript: links were already stopped by React; srcdoc was not. components/streamdown.tsx wraps Streamdown with a fixed plugin list: rehype-raw, then rehype-sanitize with GitHub's allowlist, then KaTeX and harden as before. The allowlist keeps <br>, <details>, <sub>, tables and the like, and drops scripts, frames, styles and event handlers. It also allows the renderer's own URL schemes: app://, which Spaces rewrites mentions, files and blobs to, and rowboat:// deep links, plus blob: and data: images, as Streamdown already allowed. remark-math's classes stay on <code>. All nine call sites import the wrapper. streamdown.test.tsx pins the payloads and fails if anything imports Streamdown from 'streamdown' directly. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Security fix. It affects the shipping desktop app.
The bug
Streamdown parses raw HTML in Markdown (
rehype-raw) and only hardens URLs. Any Markdown the renderer shows through it could therefore carry live HTML: a Spaces message from another member, a model's reply (prompt injection from an email or web page), a meeting summary.<iframe srcdoc="<script>…</script>">runs its script in the renderer's own origin, wherewindow.ipcis in reach. React blocks<img onerror>andjavascript:links, but nothing blockssrcdoc.Reproduced against a local Harbor: Arjun posted a message with that payload, and its script ran when another member's client rendered it.
The fix
components/streamdown.tsxwraps Streamdown with a fixed plugin list:rehype-raw, thenrehype-sanitizewith GitHub's allowlist, then KaTeX and harden as before. The list is set last, so no caller can turn the sanitizer off.<br>,<details>,<sub>, tables and the like.app://, which Spaces rewrites mentions, files and blobs to before parsing.rowboat://deep links.blob:anddata:images, which Streamdown already allowed.<code>.streamdown.test.tsxpins the attack payloads and the safe HTML, and fails if anything importsStreamdownfrom'streamdown'directly.rehype-sanitize@6.0.0. The lockfile adds only it andhast-util-sanitize.Verified
tsc -bis clean.srcdocmessage renders empty and the page title is unchanged.<br>,<sub>and<details>still render.🤖 Generated with Claude Code