惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Martin Fowler
Martin Fowler
大猫的无限游戏
大猫的无限游戏
J
Java Code Geeks
罗磊的独立博客
雷峰网
雷峰网
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
爱范儿
爱范儿
B
Blog RSS Feed
腾讯CDC
Apple Machine Learning Research
Apple Machine Learning Research
D
Docker
Recent Announcements
Recent Announcements
T
Tailwind CSS Blog
博客园 - 聂微东
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Vercel News
Vercel News
小众软件
小众软件
人人都是产品经理
人人都是产品经理
云风的 BLOG
云风的 BLOG
IT之家
IT之家
Blog — PlanetScale
Blog — PlanetScale
I
InfoQ
S
SegmentFault 最新的问题

Streamdown Documentation

Configuration FAQ Getting Started Introduction Security Animation Carets Code Blocks Components GitHub Flavored Markdown Interactivity Internationalization Memoization Migrate from react-markdown Styling Unterminated Block Parsing Typography Usage @streamdown/cjk @streamdown/code Built-in Plugins @streamdown/math @streamdown/mermaid Custom renderers
Link Safety
Vercel · 2026-04-11 · via Streamdown Documentation

Configurable confirmation modal for external links to protect users from malicious URLs.

When rendering AI-generated or user-generated content, links can pose security risks. The link safety feature adds a confirmation modal before opening external links, similar to ChatGPT's implementation.

Link safety is enabled by default. When a user clicks any link, a confirmation modal appears with:

  • The full URL being opened
  • A "Copy link" button
  • An "Open link" button
  • Close via backdrop click or Escape key

To disable the confirmation modal and allow links to open directly:

import { Streamdown } from 'streamdown';

export default function Chat({ content }) {
  return (
    <Streamdown linkSafety={{ enabled: false }}>
      {content}
    </Streamdown>
  );
}

Use the onLinkCheck callback to allow trusted domains without showing the modal:

<Streamdown
  linkSafety={{
    enabled: true,
    onLinkCheck: (url) => {
      // Return true to allow without modal (safelist)
      // Return false to show confirmation modal
      return url.startsWith('https://your-app.com') ||
             url.startsWith('https://github.com');
    }
  }}
>
  {content}
</Streamdown>

The callback receives the URL and can return:

  • true - Open the link directly without modal
  • false - Show the confirmation modal
  • Promise<boolean> - Async checks are supported

Async Safelist Check

For server-side safelist validation:

<Streamdown
  linkSafety={{
    enabled: true,
    onLinkCheck: async (url) => {
      const response = await fetch('/api/check-url', {
        method: 'POST',
        body: JSON.stringify({ url }),
      });
      const { isSafe } = await response.json();
      return isSafe;
    }
  }}
>
  {content}
</Streamdown>

Replace the default modal with your own component using renderModal:

import { Streamdown, type LinkSafetyModalProps } from 'streamdown';

function CustomLinkModal({ url, isOpen, onClose, onConfirm }: LinkSafetyModalProps) {
  if (!isOpen) return null;

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <h2>External Link</h2>
        <p>You're about to visit:</p>
        <code>{url}</code>
        <div className="actions">
          <button onClick={onClose}>Cancel</button>
          <button onClick={onConfirm}>Continue</button>
        </div>
      </div>
    </div>
  );
}

export default function Chat({ content }) {
  return (
    <Streamdown
      linkSafety={{
        enabled: true,
        renderModal: (props) => <CustomLinkModal {...props} />,
      }}
    >
      {content}
    </Streamdown>
  );
}

The renderModal function receives:

PropTypeDescription
urlstringThe URL being opened
isOpenbooleanWhether the modal is visible
onClose() => voidCall to close the modal
onConfirm() => voidCall to open the link and close the modal

Link safety works alongside content hardening. Use both for comprehensive protection:

import { Streamdown, defaultRehypePlugins } from 'streamdown';
import { harden } from 'rehype-harden';

export default function SecureChat({ content }) {
  return (
    <Streamdown
      linkSafety={{
        enabled: true,
        onLinkCheck: (url) => url.startsWith('https://trusted.com'),
      }}
      rehypePlugins={[
        defaultRehypePlugins.raw,
        [
          harden,
          {
            allowedLinkPrefixes: [
              'https://trusted.com',
              'https://github.com',
            ],
            allowedProtocols: ['https', 'mailto'],
          },
        ],
      ]}
    >
      {content}
    </Streamdown>
  );
}

This provides two layers of protection:

  1. Content hardening - Blocks or rewrites disallowed URLs at render time
  2. Link safety modal - Requires user confirmation before navigation

Import the types for custom modal implementations:

import type { LinkSafetyConfig, LinkSafetyModalProps } from 'streamdown';

const config: LinkSafetyConfig = {
  enabled: true,
  onLinkCheck: (url) => url.startsWith('https://safe.com'),
  renderModal: (props: LinkSafetyModalProps) => <CustomModal {...props} />,
};

LinkSafetyConfig

PropertyTypeDefaultDescription
enabledbooleantrueEnable link interception and modal
onLinkCheck(url: string) => boolean | Promise<boolean>-Optional safelist callback
renderModal(props: LinkSafetyModalProps) => ReactNode-Optional custom modal component

LinkSafetyModalProps

PropertyTypeDescription
urlstringThe URL to be opened
isOpenbooleanModal visibility state
onClose() => voidClose the modal without navigation
onConfirm() => voidConfirm and open the link