























Built-in content hardening and security features to protect against malicious Markdown.
Streamdown is built with security as a top priority. When rendering user-generated or AI-generated Markdown content, it's crucial to protect against malicious content, especially when dealing with content that might have been subject to prompt injection attacks.
Markdown can contain:
Streamdown uses two layers of protection:
tel: protocol supportBy default, Streamdown is configured with permissive security to allow maximum functionality:
// Default rehype-harden configuration
{
allowedImagePrefixes: ["*"], // All images allowed
allowedLinkPrefixes: ["*"], // All links allowed
allowedProtocols: ["*"], // All protocols allowed
defaultOrigin: undefined, // No origin restriction
allowDataImages: true, // Base64 images allowed
}The default rehype-sanitize schema allows http, https, irc, ircs, mailto, xmpp, and tel protocols for links.
This works well for trusted content but should be tightened for untrusted sources.
By default, all protocols are allowed. You can restrict which URL protocols are permitted:
import { Streamdown, defaultRehypePlugins } from 'streamdown';
import { harden } from 'rehype-harden';
export default function Page() {
return (
<Streamdown
rehypePlugins={[
defaultRehypePlugins.raw,
defaultRehypePlugins.sanitize,
[
harden,
{
allowedProtocols: [
'http',
'https',
'mailto',
],
},
],
]}
>
{markdown}
</Streamdown>
);
}When overriding rehypePlugins, always include defaultRehypePlugins.sanitize to preserve XSS protection. The rehypePlugins prop replaces the entire default array — it does not merge.
This is useful for security-sensitive applications where you want to prevent custom protocol schemes like javascript:, data:, or desktop app protocols.
To enable custom protocol schemes like postman://, vscode://, or slack://, include them in the allowedProtocols array:
{
allowedProtocols: [
'http',
'https',
'postman',
'vscode',
'slack',
],
}Limit which domains users can link to:
import { Streamdown, defaultRehypePlugins } from 'streamdown';
import { harden } from 'rehype-harden';
export default function Page() {
return (
<Streamdown
rehypePlugins={[
defaultRehypePlugins.raw,
defaultRehypePlugins.sanitize,
[
harden,
{
defaultOrigin: 'https://streamdown.ai',
allowedLinkPrefixes: [
'https://streamdown.ai',
'https://github.com',
'https://vercel.com',
],
},
],
]}
>
{markdown}
</Streamdown>
);
}Any links not matching the allowed prefixes will be rewritten to point to the defaultOrigin.
With the above configuration:
[Safe link](https://github.com/vercel/streamdown)
[Unsafe link](https://malicious-site.com)Results in:
Similarly, restrict which domains can serve images:
<Streamdown
rehypePlugins={[
defaultRehypePlugins.raw,
defaultRehypePlugins.sanitize,
[
harden,
{
allowedImagePrefixes: [
'https://your-cdn.com',
'https://trusted-images.com',
],
allowDataImages: false, // Disable base64 images
},
],
]}
>
{markdown}
</Streamdown>Base64-encoded images (data:image/...) can be disabled:
This prevents embedding arbitrary image data in Markdown, which could be used for:
When using AI-generated content, models can be manipulated to include malicious links or content. Here's a production-ready configuration:
import { Streamdown, defaultRehypePlugins } from 'streamdown';
import { harden } from 'rehype-harden';
export default function ChatMessage({ content, isAIGenerated }) {
const securityConfig = isAIGenerated ? {
defaultOrigin: 'https://your-app.com',
allowedLinkPrefixes: [
'https://your-app.com',
'https://docs.your-app.com',
'https://github.com',
],
allowedImagePrefixes: [
'https://your-cdn.com',
],
allowedProtocols: [
'http',
'https',
'mailto',
],
allowDataImages: false,
} : {
// More permissive for user content
allowedLinkPrefixes: ['*'],
allowedImagePrefixes: ['*'],
allowedProtocols: ['*'],
};
return (
<Streamdown
rehypePlugins={[
defaultRehypePlugins.raw,
defaultRehypePlugins.sanitize,
[harden, securityConfig],
]}
>
{content}
</Streamdown>
);
}By default, Streamdown's sanitizer strips unknown HTML tags (while preserving their content). If you need to render custom tags like <ref> or <mention>, use the allowedTags prop:
<Streamdown
allowedTags={{
ref: ["note_id"], // Allow <ref> with note_id attribute
mention: ["user_id"], // Allow <mention> with user_id attribute
}}
components={{
ref: (props) => <NoteBadge noteId={props.note_id} />,
mention: (props) => <UserMention userId={props.user_id} />,
}}
>
{markdown}
</Streamdown>Only attributes explicitly listed in allowedTags are preserved—all other attributes are stripped for security. See the Styling documentation for more details.
The allowedTags prop only works with the default rehype plugins. If you provide custom rehypePlugins, you must configure sanitization yourself.
Streamdown supports raw HTML through rehype-raw. To disable HTML entirely:
import { Streamdown, defaultRehypePlugins } from 'streamdown';
export default function Page() {
return (
<Streamdown
rehypePlugins={[
// Omit defaultRehypePlugins.raw to disable HTML
defaultRehypePlugins.sanitize,
defaultRehypePlugins.harden,
]}
>
{markdown}
</Streamdown>
);
}Without rehype-raw, HTML tags will be escaped and displayed as text.
Control how relative URLs are handled:
{
defaultOrigin: 'https://your-app.com'
}Relative URLs will be resolved against this origin:
[Relative link](/docs/guide)Becomes: https://your-app.com/docs/guide
For URL-level control without writing a rehype plugin, use the urlTransform prop. This runs on every URL in the rendered Markdown (links, images, etc.) and matches the react-markdown API.
By default, defaultUrlTransform is a passthrough — URL security is already handled by rehype-sanitize and rehype-harden. Use urlTransform when you need custom URL rewriting beyond what those plugins provide.
import { Streamdown, defaultUrlTransform } from 'streamdown';
// Proxy images through your CDN
<Streamdown
urlTransform={(url, key, node) => {
if (key === 'src') {
return `https://your-cdn.com/proxy?url=${encodeURIComponent(url)}`;
}
return defaultUrlTransform(url, key, node);
}}
>
{markdown}
</Streamdown>To completely ignore raw HTML in Markdown (rather than escaping it), use the skipHtml prop:
<Streamdown skipHtml>
{markdown}
</Streamdown>For advanced URL handling beyond what urlTransform and rehype-harden provide, you can create a custom rehype plugin. This gives you full control over URL transformation and validation in your markdown content.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。