Security
vue-stream-markdown 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
vue-stream-markdown implements URL sanitization and validation based on the source code from rehype-harden to protect against malicious content before rendering.
Default Security
By default, vue-stream-markdown is configured with permissive security to allow maximum functionality:
// 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:
<script setup lang="ts">
import type { HardenOptions } from 'vue-stream-markdown'
import { Markdown } from 'vue-stream-markdown'
const hardenOptions: HardenOptions = {
allowedProtocols: [
'http',
'https',
'mailto',
],
}
</script>
<template>
<Markdown :harden-options="hardenOptions" :content="markdown" />
</template>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:
{
allowedProtocols: [
'http',
'https',
'postman',
'vscode',
'slack',
],
}Restricting Links
Limit which domains users can link to:
<script setup lang="ts">
import type { HardenOptions } from 'vue-stream-markdown'
import { Markdown } from 'vue-stream-markdown'
const hardenOptions: HardenOptions = {
defaultOrigin: 'https://vue-stream-markdown.com',
allowedLinkPrefixes: [
'https://vue-stream-markdown.com',
'https://github.com',
'https://vercel.com',
],
}
</script>
<template>
<Markdown :harden-options="hardenOptions" :content="markdown" />
</template>Any links not matching the allowed prefixes will be rewritten to point to the defaultOrigin.
Example
With the above configuration:
[Safe link](https://github.com/vercel/vue-stream-markdown)
[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:
<script setup lang="ts">
import type { HardenOptions } from 'vue-stream-markdown'
import { Markdown } from 'vue-stream-markdown'
const hardenOptions: HardenOptions = {
allowedImagePrefixes: [
'https://your-cdn.com',
'https://trusted-images.com',
],
allowDataImages: false, // Disable base64 images
}
</script>
<template>
<Markdown :harden-options="hardenOptions" :content="markdown" />
</template>Data Images
Base64-encoded images (data:image/...) can be disabled:
allowDataImages: falseThis 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:
<script setup lang="ts">
import type { HardenOptions } from 'vue-stream-markdown'
import { computed } from 'vue'
import { Markdown } from 'vue-stream-markdown'
const props = defineProps<{
content: string
isAIGenerated: boolean
}>()
const hardenOptions = computed<HardenOptions>(() => {
if (props.isAIGenerated) {
return {
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,
}
}
else {
// More permissive for user content
return {
allowedLinkPrefixes: ['*'],
allowedImagePrefixes: ['*'],
allowedProtocols: ['*'],
}
}
})
</script>
<template>
<Markdown :harden-options="hardenOptions" :content="content" />
</template>Relative URLs
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