Skip to content

Security: Unsafe HTML/Markdown Rendering via Streamdown Component#2966

Open
tomaioo wants to merge 1 commit into
MCPJam:mainfrom
tomaioo:fix/security/unsafe-html-markdown-rendering-via-strea
Open

Security: Unsafe HTML/Markdown Rendering via Streamdown Component#2966
tomaioo wants to merge 1 commit into
MCPJam:mainfrom
tomaioo:fix/security/unsafe-html-markdown-rendering-via-strea

Conversation

@tomaioo

@tomaioo tomaioo commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Security: Unsafe HTML/Markdown Rendering via Streamdown Component

Problem

Severity: High | File: chat-ui/src/internal/markdown.tsx:L1

The Markdown component in chat-ui/src/internal/markdown.tsx renders untrusted model text using Streamdown without any explicit sanitization or HTML escaping. While the comment mentions 'safe Streamdown render', there is no evidence of DOMPurify or similar sanitization being applied. If Streamdown renders raw HTML, this creates a Cross-Site Scripting (XSS) vulnerability when displaying AI-generated content that may contain malicious HTML/JavaScript payloads.

Solution

Verify that Streamdown performs HTML sanitization by default. If not, integrate a sanitization library like DOMPurify or use a markdown parser that sanitizes HTML by default (e.g., marked with sanitize-html). Consider rendering markdown to a safe AST first before converting to HTML.

Changes

  • chat-ui/src/internal/markdown.tsx (modified)

Summary by cubic

Prevent XSS by disabling raw HTML rendering in the Markdown component via Streamdown. Untrusted content is now rendered without interpreting HTML.

  • Bug Fixes
    • Set <Streamdown rawHtml={false}> in chat-ui/src/internal/markdown.tsx to block raw HTML from being rendered.

Written for commit 96a3313. Summary will update on new commits.

Review in cubic

The `Markdown` component in `chat-ui/src/internal/markdown.tsx` renders untrusted model text using `Streamdown` without any explicit sanitization or HTML escaping. While the comment mentions 'safe Streamdown render', there is no evidence of DOMPurify or similar sanitization being applied. If `Streamdown` renders raw HTML, this creates a Cross-Site Scripting (XSS) vulnerability when displaying AI-generated content that may contain malicious HTML/JavaScript payloads.

Signed-off-by: tomaioo <203048277+tomaioo@users.noreply.github.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@dosubot dosubot Bot added size:XS This PR changes 0-9 lines, ignoring generated files. bug Something isn't working labels Jun 29, 2026
@chelojimenez

Copy link
Copy Markdown
Contributor

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 1 file

Re-trigger cubic

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

In chat-ui/src/internal/markdown.tsx, the Streamdown component now receives rawHtml={false} explicitly, replacing the prior implicit reliance on the library's default behavior for raw HTML rendering.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@chat-ui/src/internal/markdown.tsx`:
- Line 20: The Markdown renderer configuration is using the wrong Streamdown
prop, so HTML is still not being disabled as intended. Update the Streamdown
usage in markdown.tsx to use skipHtml instead of rawHtml={false}, and keep the
component wired around the existing content rendering so the HTML-skipping
behavior takes effect correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: df457663-56f1-459a-b90b-23f6a5d55d17

📥 Commits

Reviewing files that changed from the base of the PR and between 25d7814 and 96a3313.

📒 Files selected for processing (1)
  • chat-ui/src/internal/markdown.tsx

return (
<div className={cn("mcpjam-chat-markdown [overflow-wrap:anywhere]", className)}>
<Streamdown>{content}</Streamdown>
<Streamdown rawHtml={false}>{content}</Streamdown>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For streamdown version 2.1.0, what prop disables raw HTML rendering on the React component: skipHtmlorrawHtml?

💡 Result:

For Streamdown version 2.1.0, the prop that disables raw HTML rendering is skipHtml [1][2][3]. When the skipHtml prop is set to true, Streamdown completely ignores raw HTML in the Markdown source rather than escaping it or rendering it [2][4][3]. This behavior aligns with the react-markdown API, as Streamdown is designed to be a drop-in replacement [5][1][3]. Additionally, you can completely remove raw HTML support by omitting the default rehype-raw plugin from the rehypePlugins configuration [2][4].

Citations:


Use skipHtml here. rawHtml isn’t the Streamdown prop for disabling HTML; skipHtml is. Keeping rawHtml={false} leaves HTML rendering enabled.

Suggested change
-      <Streamdown rawHtml={false}>{content}</Streamdown>
+      <Streamdown skipHtml>{content}</Streamdown>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Streamdown rawHtml={false}>{content}</Streamdown>
<Streamdown skipHtml>{content}</Streamdown>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@chat-ui/src/internal/markdown.tsx` at line 20, The Markdown renderer
configuration is using the wrong Streamdown prop, so HTML is still not being
disabled as intended. Update the Streamdown usage in markdown.tsx to use
skipHtml instead of rawHtml={false}, and keep the component wired around the
existing content rendering so the HTML-skipping behavior takes effect correctly.

@chelojimenez

Copy link
Copy Markdown
Contributor

hi @tomaioo, Streamdown already sanitizes HTML by default (rehype-sanitize + rehype-harden), so there's no XSS hole. would like to know if this was considered in your investigation

@tomaioo

tomaioo commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Hi @chelojimenez, thanks for clarifying. I wasn't aware that Streamdown bundles rehype-sanitize and rehype-harden by default—my review focused on the absence of an explicit sanitization step in our code. Given this, the rawHtml={false} change is still a reasonable defense-in-depth measure to limit the attack surface, but I agree the original severity assessment was overstated.

10 similar comments
@tomaioo

tomaioo commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Hi @chelojimenez, thanks for clarifying. I wasn't aware that Streamdown bundles rehype-sanitize and rehype-harden by default—my review focused on the absence of an explicit sanitization step in our code. Given this, the rawHtml={false} change is still a reasonable defense-in-depth measure to limit the attack surface, but I agree the original severity assessment was overstated.

@tomaioo

tomaioo commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Hi @chelojimenez, thanks for clarifying. I wasn't aware that Streamdown bundles rehype-sanitize and rehype-harden by default—my review focused on the absence of an explicit sanitization step in our code. Given this, the rawHtml={false} change is still a reasonable defense-in-depth measure to limit the attack surface, but I agree the original severity assessment was overstated.

@tomaioo

tomaioo commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Hi @chelojimenez, thanks for clarifying. I wasn't aware that Streamdown bundles rehype-sanitize and rehype-harden by default—my review focused on the absence of an explicit sanitization step in our code. Given this, the rawHtml={false} change is still a reasonable defense-in-depth measure to limit the attack surface, but I agree the original severity assessment was overstated.

@tomaioo

tomaioo commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Hi @chelojimenez, thanks for clarifying. I wasn't aware that Streamdown bundles rehype-sanitize and rehype-harden by default—my review focused on the absence of an explicit sanitization step in our code. Given this, the rawHtml={false} change is still a reasonable defense-in-depth measure to limit the attack surface, but I agree the original severity assessment was overstated.

@tomaioo

tomaioo commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Hi @chelojimenez, thanks for clarifying. I wasn't aware that Streamdown bundles rehype-sanitize and rehype-harden by default—my review focused on the absence of an explicit sanitization step in our code. Given this, the rawHtml={false} change is still a reasonable defense-in-depth measure to limit the attack surface, but I agree the original severity assessment was overstated.

@tomaioo

tomaioo commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

Hi @chelojimenez, thanks for clarifying. I wasn't aware that Streamdown bundles rehype-sanitize and rehype-harden by default—my review focused on the absence of an explicit sanitization step in our code. Given this, the rawHtml={false} change is still a reasonable defense-in-depth measure to limit the attack surface, but I agree the original severity assessment was overstated.

@tomaioo

tomaioo commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

Hi @chelojimenez, thanks for clarifying. I wasn't aware that Streamdown bundles rehype-sanitize and rehype-harden by default—my review focused on the absence of an explicit sanitization step in our code. Given this, the rawHtml={false} change is still a reasonable defense-in-depth measure to limit the attack surface, but I agree the original severity assessment was overstated.

@tomaioo

tomaioo commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

Hi @chelojimenez, thanks for clarifying. I wasn't aware that Streamdown bundles rehype-sanitize and rehype-harden by default—my review focused on the absence of an explicit sanitization step in our code. Given this, the rawHtml={false} change is still a reasonable defense-in-depth measure to limit the attack surface, but I agree the original severity assessment was overstated.

@tomaioo

tomaioo commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

Hi @chelojimenez, thanks for clarifying. I wasn't aware that Streamdown bundles rehype-sanitize and rehype-harden by default—my review focused on the absence of an explicit sanitization step in our code. Given this, the rawHtml={false} change is still a reasonable defense-in-depth measure to limit the attack surface, but I agree the original severity assessment was overstated.

@tomaioo

tomaioo commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Hi @chelojimenez, thanks for clarifying. I wasn't aware that Streamdown bundles rehype-sanitize and rehype-harden by default—my review focused on the absence of an explicit sanitization step in our code. Given this, the rawHtml={false} change is still a reasonable defense-in-depth measure to limit the attack surface, but I agree the original severity assessment was overstated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size:XS This PR changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants