Взаимодействие с DOMPurify

Библиотека Markdown-it предоставляет мощный парсер Markdown в JavaScript, превращая текст с разметкой в валидный HTML. Однако при отображении пользовательского контента важно учитывать безопасность, чтобы предотвратить XSS-атаки. Для этого используется DOMPurify — инструмент для очистки HTML от вредоносного кода.

Классический сценарий работы выглядит следующим образом: Markdown-текст обрабатывается через Markdown-it, результат конвертируется в HTML, после чего проходит через DOMPurify для фильтрации небезопасных элементов и атрибутов.

import MarkdownIt from 'markdown-it';
import DOMPurify from 'dompurify';

const md = new MarkdownIt();

const rawMarkdown = `# Пример заголовка
<script>alert('XSS')</script>
**Жирный текст**`;

const html = md.render(rawMarkdown);
const safeHtml = DOMPurify.sanitize(html);

document.getElementById('content').innerHTML = safeHtml;

В этом примере <script> тег автоматически удаляется DOMPurify, а Markdown-разметка корректно преобразуется в HTML.


Настройка правил очистки DOMPurify

DOMPurify предоставляет широкие возможности настройки, включая:

  • ALLOWLIST тегов и атрибутов — ограничение HTML только разрешёнными элементами.
  • Пользовательские хуки — возможность модифицировать поведение очистки для определённых тегов.
  • Разрешение URL — контроль за допустимыми протоколами (например, только https:).

Пример ограничения атрибутов:

const cleanHtml = DOMPurify.sanitize(html, {
  ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'ul', 'li', 'h1', 'h2', 'h3'],
  ALLOWED_ATTR: ['href', 'title']
});

Это особенно важно при интеграции Markdown с пользовательскими ссылками, чтобы не допустить открытие вредоносных URL.


Использование Markdown-it плагинов с DOMPurify

Markdown-it поддерживает систему плагинов, которая расширяет синтаксис и добавляет новые возможности, такие как таблицы, чекбоксы, математические формулы и многое другое. После применения плагина результат также необходимо фильтровать через DOMPurify.

Пример с плагином таблиц:

import markdownItTable from 'markdown-it-multimd-table';

const md = new MarkdownIt().use(markdownItTable);

const tableMarkdown = `
| Имя  | Возраст |
|------|---------|
| Иван | 25      |
`;

const htmlTable = md.render(tableMarkdown);
const safeHtmlTable = DOMPurify.sanitize(htmlTable);

Даже если плагин генерирует сложные HTML-структуры, DOMPurify гарантирует удаление нежелательных тегов или атрибутов.


Производительность и оптимизация

При рендеринге больших объёмов Markdown важно учитывать эффективность фильтрации. DOMPurify достаточно быстрый, но обработка тысяч элементов может быть ресурсоёмкой. Рекомендуется:

  1. Кэшировать уже очищенные строки, если контент повторяется.
  2. Ограничивать глубину вложенности HTML, чтобы DOMPurify быстрее обрабатывал дерево.
  3. Применять DOMPurify только к динамическому контенту, а статический HTML можно выводить без повторной фильтрации.
let cache = new Map();

function renderMarkdownSafe(markdown) {
  if (cache.has(markdown)) return cache.get(markdown);
  const html = md.render(markdown);
  const safe = DOMPurify.sanitize(html);
  cache.set(markdown, safe);
  return safe;
}

Хуки DOMPurify для расширенного контроля

DOMPurify позволяет использовать хуки до и после очистки, что даёт возможность изменять HTML или добавлять собственную валидацию. Основные хуки:

  • beforeSanitizeElements — вызывается перед обработкой тегов.
  • uponSanitizeElement — вызывается при проверке каждого элемента.
  • uponSanitizeAttribute — вызывается для каждого атрибута элемента.
  • afterSanitizeElements — вызывается после очистки элементов.

Пример удаления всех <img> тегов с небезопасными src:

DOMPurify.addHook('uponSanitizeAttribute', (node, data) => {
  if (data.attrName === 'src' && node.tagName === 'IMG') {
    if (!/^https?:\/\//.test(data.attrValue)) {
      data.keepAttr = false;
    }
  }
});

Это обеспечивает гибкость при работе с Markdown, особенно когда пользователь может вставлять HTML или использовать расширенные плагины Markdown-it.


Интеграция с фреймворками

При работе с фреймворками вроде React, Vue или Svelte необходимо учитывать особенности рендеринга HTML:

  • В React нужно использовать dangerouslySetInnerHTML, но после очистки через DOMPurify.
  • Во Vue используется директива v-html.
  • В Svelte применяется {@html ...} для вставки HTML.

Пример с React:

function MarkdownRenderer({ content }) {
  const html = md.render(content);
  const safeHtml = DOMPurify.sanitize(html);
  return <div dangerouslySetInnerHTML={{ __html: safeHtml }} />;
}

Это предотвращает XSS даже при динамическом контенте, генерируемом пользователями.


Практические советы по безопасности

  1. Сочетать Markdown-it с DOMPurify всегда, если контент от пользователей.
  2. Ограничивать разрешённые теги и атрибуты, особенно при использовании сторонних плагинов.
  3. Регулярно обновлять версии библиотек, так как новые версии DOMPurify исправляют потенциальные уязвимости.
  4. Тестировать рендеринг сложных конструкций Markdown, чтобы убедиться, что очистка не ломает форматирование.

Markdown-it в связке с DOMPurify обеспечивает безопасное и гибкое отображение пользовательского Markdown, позволяя сохранять богатую разметку, таблицы, ссылки и другие расширенные элементы, одновременно исключая возможность внедрения вредоносного кода.