Перейти к содержанию
Acecore
Содержание
Безопасный рендеринг Markdown-ссылок в ответах AI-чата

Если AI-чат отвечает Подробнее см. [Services]( /services/ ), ссылка может не отрендериться, а исходный Markdown останется на экране.

Acecore столкнулся с этим в контактном AI-чате и поправил renderer в PR с исправлением Markdown-ссылок.

Эта статья использует небольшое исправление как вход в тему безопасного превращения AI-ответов в DOM.

Ответы AI не являются доверенным HTML

Вывод модели нужно считать текстом.

В чате полезны ссылки, жирный текст и списки. Но innerHTML заставляет браузер интерпретировать любую строку, которую произвела модель.

Не нужно реализовывать весь Markdown. Нужен небольшой renderer, который распознает только поддерживаемые элементы и создает безопасные DOM-узлы.

Проблема не только в пробелах

Конкретная ошибка была в ссылке:

[Services](/services/)

Строгая regex часто считает, что URL не содержит пробелов:

;/\[([^\]]+)\]\(([^)\s]+)\)/

[^)\s]+ отклоняет пробелы, поэтому ( /services/ ) не распознается. Исправление допускает пробелы внутри скобок, а затем нормализует значение.

;/\[([^\]]+)\]\(\s*([^)]+?)\s*\)/

Но ослабить parser недостаточно. Нормализованное значение обязательно нужно проверить.

Делайте trim перед проверкой href

Порядок должен быть таким:

  1. Извлечь label и raw href из Markdown
  2. Применить trim() к raw href
  3. Проверить href по allowlist
  4. Создать <a> только если href разрешен
const href = String(rawHref || '').trim()

if (label && isSafeMarkdownHref(href)) {
  const link = document.createElement('a')
  link.href = href
  link.rel = 'noopener noreferrer'

  if (/^https?:\/\//i.test(href)) {
    link.target = '_blank'
  }

  link.textContent = label
  parent.appendChild(link)
}

Проверяемое значение должно быть тем же, что попадает в DOM.

Allowlist зависит от продукта

Каждый сайт должен решить, какие URL может показывать AI.

ТипПримерРешение
Внутренний путь/services/Разрешить
Тот же originhttps://acecore.net/...Разрешить
Официальный LINEhttps://lin.ee/...Разрешить как официальный канал
mailtomailto:[email protected]Только фиксированный адрес
teltel:05088902788Только фиксированный номер
Другие внешниеЛюбой URLПо умолчанию не ссылать
function isSafeMarkdownHref(href) {
  if (href.startsWith('/')) return true

  try {
    const url = new URL(href, window.location.origin)
    if (url.origin === window.location.origin) return true
    if (url.hostname === 'acecore.net') return true
    if (url.hostname === 'lin.ee') return true
  } catch {
    return false
  }

  return href === 'mailto:[email protected]' || href === 'tel:05088902788'
}

Рекрутинговый сайт может разрешать job boards, SaaS может разрешать документацию и status page. Функция должна отражать политику продукта.

Fallback в текст

Если ссылка не проходит проверку, удаление не всегда лучший вариант.

В контактном AI-чате текстовый fallback сохраняет контекст для пользователя и помогает разработчикам увидеть, что модель пыталась вывести.

Renderer должен не только создавать безопасные ссылки, но и безопасно отказывать.

Тестируйте плохие случаи

Минимальный набор:

ВводОжидаемый результат
[Services](/services/)Внутренняя ссылка
[Services]( /services/ )Внутренняя ссылка после trim
[LINE]( https://lin.ee/example )Разрешенная внешняя ссылка
[Bad](javascript:alert(1))Не превращается в ссылку
[External](https://example.com/)Не ссылка, если домен не разрешен
[Broken](/services/Отображается как текст

В PR #99 было проверено, что варианты с пробелами и без них ведут к ожидаемому URL.

Не реализуйте весь Markdown по умолчанию

Для чата обычно достаточно:

  • Абзацы
  • Списки
  • Жирный текст
  • Inline-code
  • Ссылки

Таблицы, изображения, raw HTML и footnotes быстро расширяют ответственность renderer. Даже с библиотекой политика HTML и URL остается отдельным решением.

Итог

Рендеринг Markdown-ссылок в AI-ответах выглядит как небольшая UI-правка, но на деле задает границу доверия к выводу модели.

Практическое правило: сначала текст, маленькое подмножество, trim перед проверкой, строгий allowlist и безопасный fallback.

Поток рендеринга ссылок

Text

Сначала рассматривать ответ модели как обычный текст.

Parse

Находить только те Markdown-элементы, которые реально поддерживает чат.

Validate

Делать trim для href и разрешать только внутренние URL или одобренные домены.

Render

Создавать безопасные элементы через DOM API, не через innerHTML.

Какие решения нужно разделять

Слабый рендеринг

  • Вставлять ответы AI прямо в innerHTML
  • Пытаться сразу реализовать весь Markdown
  • Не распознавать ссылки с пробелами вокруг URL
  • Обрабатывать внешние URL и javascript: одинаково

Малый и безопасный рендеринг

  • Принимать ответы как текст и превращать в DOM только нужное
  • Поддерживать только подмножество Markdown для чата
  • Проверять URL после trim
  • Оставлять запрещенные URL обычным текстом
Чеклист внедрения
  • Выполнено: Не доверять ответам AI как HTML
  • Выполнено: Разрешать пробелы вокруг URL в Markdown-ссылках
  • Выполнено: Всегда делать trim href перед проверкой
  • Выполнено: Разрешать только внутренние пути, текущий origin и нужные внешние домены
  • Выполнено: Явно задавать target и rel для внешних ссылок
  • Выполнено: Сохранять запрещенные ссылки как текст
  • Выполнено: Тестировать опасные URL и сломанный Markdown
Частые вопросы
Достаточно ли markdown-it или marked?
Даже с библиотекой нужно отдельно решить, как обрабатывать HTML, какие цели ссылок разрешены, как добавлять target и rel и как отклонять опасные URL. Для чата небольшого собственного renderer может быть достаточно.
Опасно ли разрешать пробелы вокруг URL?
Риск не в пробелах, а в том, что разрешается после trim. Проверка нормализованного href сохраняет allowlist строгим.
Нужно ли удалять запрещенные URL?
Обычно текстовый fallback проще для отладки и сохраняет контекст. Более строгая политика может удалить всю ссылку.

Комментарии

Загрузка комментариев...

Ссылки, email-адреса и рекламный текст публиковать нельзя.

G

Gui

Генеральный директор Acecore. Руководит бизнес-системами, вебом, базами данных и инфраструктурой, качеством и внедрением ИИ от формулирования бизнес-задач до проектирования, запуска и дальнейшего улучшения. Опирается на практическую экспертизу C#/.NET и также учитывает PHP/JavaScript, SQL Server/PostgreSQL/MySQL и Linux/Windows Server, проектируя требования, технологический выбор, стандарты качества и GitHub-ориентированные процессы разработки как единую систему. Встраивает генеративный ИИ в процессы разработки, проверки и организации информации как практическую основу, помогающую небольшим командам быстрее и надежнее достигать результата.

Формулирование бизнес-задачТехнологический выборПроектирование системC#/.NETПроектирование БД/инфраструктурыGitHub-процессы разработкиГенеративный ИИПроектирование ИИ-процессовПроектирование качестваИнтеграция на местах

Хотите узнать больше о наших услугах?

Мы обеспечиваем комплексную поддержку: разработка систем, веб-дизайн, графический дизайн и IT-образование.

Похожие статьи

Поиск статей