mirror of
https://github.com/vercel/streamdown.git
synced 2026-09-19 02:18:37 +08:00
a12de57496
* allow custom tags in components * comment * tests * docs * lint * changeset
283 lines
6.8 KiB
Plaintext
283 lines
6.8 KiB
Plaintext
---
|
|
title: Security
|
|
description: 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.
|
|
|
|
## Why Security Matters
|
|
|
|
Markdown can contain:
|
|
- **Links to malicious sites** - Phishing or malware distribution
|
|
- **External images** - Privacy tracking or CSRF attacks
|
|
- **HTML content** - XSS vulnerabilities
|
|
- **JavaScript execution** - Code injection
|
|
- **Prompt injection** - AI models manipulated to include harmful content
|
|
|
|
Streamdown uses [rehype-harden](https://github.com/vercel-labs/markdown-sanitizers) to sanitize and validate content before rendering.
|
|
|
|
## Default Security
|
|
|
|
By default, Streamdown is configured with **permissive security** to allow maximum functionality:
|
|
|
|
```tsx
|
|
// Default configuration
|
|
{
|
|
allowedImagePrefixes: ["*"], // All images allowed
|
|
allowedLinkPrefixes: ["*"], // All links allowed
|
|
allowedProtocols: ["*"], // All protocols allowed
|
|
defaultOrigin: undefined, // No origin restriction
|
|
allowDataImages: true, // Base64 images allowed
|
|
}
|
|
```
|
|
|
|
This works well for trusted content but should be tightened for untrusted sources.
|
|
|
|
## Restricting Protocols
|
|
|
|
By default, all protocols are allowed. You can restrict which URL protocols are permitted:
|
|
|
|
```tsx
|
|
import { Streamdown, defaultRehypePlugins } from 'streamdown';
|
|
import { harden } from 'rehype-harden';
|
|
|
|
export default function Page() {
|
|
return (
|
|
<Streamdown
|
|
rehypePlugins={[
|
|
defaultRehypePlugins.raw,
|
|
[
|
|
harden,
|
|
{
|
|
allowedProtocols: [
|
|
'http',
|
|
'https',
|
|
'mailto',
|
|
],
|
|
},
|
|
],
|
|
]}
|
|
>
|
|
{markdown}
|
|
</Streamdown>
|
|
);
|
|
}
|
|
```
|
|
|
|
This is useful for security-sensitive applications where you want to prevent custom protocol schemes like `javascript:`, `data:`, or desktop app protocols.
|
|
|
|
### Custom Protocol Schemes
|
|
|
|
To enable custom protocol schemes like `postman://`, `vscode://`, or `slack://`, include them in the `allowedProtocols` array:
|
|
|
|
```tsx
|
|
{
|
|
allowedProtocols: [
|
|
'http',
|
|
'https',
|
|
'postman',
|
|
'vscode',
|
|
'slack',
|
|
],
|
|
}
|
|
```
|
|
|
|
## Restricting Links
|
|
|
|
Limit which domains users can link to:
|
|
|
|
```tsx
|
|
import { Streamdown, defaultRehypePlugins } from 'streamdown';
|
|
import { harden } from 'rehype-harden';
|
|
|
|
export default function Page() {
|
|
return (
|
|
<Streamdown
|
|
rehypePlugins={[
|
|
defaultRehypePlugins.raw,
|
|
[
|
|
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`.
|
|
|
|
### Example
|
|
|
|
With the above configuration:
|
|
|
|
```markdown
|
|
[Safe link](https://github.com/vercel/streamdown)
|
|
[Unsafe link](https://malicious-site.com)
|
|
```
|
|
|
|
Results in:
|
|
- Safe link: Works normally
|
|
- Unsafe link: Renders as [blocked]
|
|
|
|
## Restricting Images
|
|
|
|
Similarly, restrict which domains can serve images:
|
|
|
|
```tsx
|
|
<Streamdown
|
|
rehypePlugins={[
|
|
defaultRehypePlugins.raw,
|
|
[
|
|
harden,
|
|
{
|
|
allowedImagePrefixes: [
|
|
'https://your-cdn.com',
|
|
'https://trusted-images.com',
|
|
],
|
|
allowDataImages: false, // Disable base64 images
|
|
},
|
|
],
|
|
]}
|
|
>
|
|
{markdown}
|
|
</Streamdown>
|
|
```
|
|
|
|
### Data Images
|
|
|
|
Base64-encoded images (`data:image/...`) can be disabled:
|
|
|
|
```tsx
|
|
allowDataImages: false
|
|
```
|
|
|
|
This prevents embedding arbitrary image data in Markdown, which could be used for:
|
|
- Tracking pixels
|
|
- Large embedded files
|
|
- Malicious payloads
|
|
|
|
## Protecting Against Prompt Injection
|
|
|
|
When using AI-generated content, models can be manipulated to include malicious links or content. Here's a production-ready configuration:
|
|
|
|
```tsx
|
|
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,
|
|
[harden, securityConfig],
|
|
]}
|
|
>
|
|
{content}
|
|
</Streamdown>
|
|
);
|
|
}
|
|
```
|
|
|
|
## Custom HTML Tags
|
|
|
|
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:
|
|
|
|
```tsx
|
|
<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](/docs/styling#custom-tags) for more details.
|
|
|
|
<Callout type="warning">
|
|
The `allowedTags` prop only works with the default rehype plugins. If you provide custom `rehypePlugins`, you must configure sanitization yourself.
|
|
</Callout>
|
|
|
|
## HTML Content
|
|
|
|
Streamdown supports raw HTML through `rehype-raw`. To disable HTML entirely:
|
|
|
|
```tsx
|
|
import { Streamdown, defaultRehypePlugins } from 'streamdown';
|
|
|
|
export default function Page() {
|
|
return (
|
|
<Streamdown
|
|
rehypePlugins={[
|
|
// Omit defaultRehypePlugins.raw
|
|
defaultRehypePlugins.harden,
|
|
]}
|
|
>
|
|
{markdown}
|
|
</Streamdown>
|
|
);
|
|
}
|
|
```
|
|
|
|
Without `rehype-raw`, HTML tags will be escaped and displayed as text.
|
|
|
|
## Relative URLs
|
|
|
|
Control how relative URLs are handled:
|
|
|
|
```tsx
|
|
{
|
|
defaultOrigin: 'https://your-app.com'
|
|
}
|
|
```
|
|
|
|
Relative URLs will be resolved against this origin:
|
|
|
|
```markdown
|
|
[Relative link](/docs/guide)
|
|
```
|
|
|
|
Becomes: `https://your-app.com/docs/guide`
|
|
|
|
## Advanced URL Handling
|
|
|
|
For advanced URL handling beyond what `rehype-harden` provides, you can create a custom rehype plugin. This gives you full control over URL transformation and validation in your markdown content.
|