Рекомендации по безопасности

Контекст безопасности

Markdown-it — это высокопроизводительная библиотека для парсинга Markdown в HTML. При её использовании критически важно учитывать уязвимости XSS (Cross-Site Scripting) и возможность внедрения вредоносного кода через пользовательский ввод. По умолчанию Markdown-it не фильтрует HTML-теги, что открывает потенциал для атак. Любой HTML, включённый в Markdown, будет напрямую вставлен в итоговый HTML-код.

Ограничение встроенного HTML

Для снижения риска XSS следует отключать обработку встроенного HTML, если она не требуется. В Markdown-it это делается через опцию html:

const md = require('markdown-it')({
  html: false,   // блокировка встроенного HTML
  linkify: true,
  typographer: true
});
  • html: false запрещает вставку HTML-тегов.
  • linkify: true автоматически превращает URL в ссылки.
  • typographer: true включает преобразование типографических символов.

Использование безопасного рендеринга ссылок

Markdown часто содержит внешние ссылки. Чтобы предотвратить фишинг и небезопасные ссылки, важно добавлять атрибуты rel="noopener noreferrer" и target="_blank". Markdown-it позволяет переопределять рендереры через правила:

const defaultRender = md.renderer.rules.link_open || function(tokens, idx, options, env, self) {
  return self.renderToken(tokens, idx, options);
};

md.renderer.rules.link_open = function(tokens, idx, options, env, self) {
  const aIndex = tokens[idx].attrIndex('target');
  if (aIndex < 0) {
    tokens[idx].attrPush(['target', '_blank']);
  } else {
    tokens[idx].attrs[aIndex][1] = '_blank';
  }

  const relIndex = tokens[idx].attrIndex('rel');
  if (relIndex < 0) {
    tokens[idx].attrPush(['rel', 'noopener noreferrer']);
  } else {
    tokens[idx].attrs[relIndex][1] = 'noopener noreferrer';
  }

  return defaultRender(tokens, idx, options, env, self);
};

Это гарантирует, что все внешние ссылки будут открываться в новой вкладке безопасно, предотвращая доступ к объекту window.opener.

Фильтрация опасного HTML

Если включён HTML, необходимо использовать сильные HTML-саниайзеры, такие как DOMPurify:

const DOMPurify = require('dompurify')(new JSDOM('').window);

const unsafeHTML = md.render(userInput);
const safeHTML = DOMPurify.sanitize(unsafeHTML);

Это предотвращает внедрение <script>, <iframe> и других опасных элементов.

Настройка разрешённых элементов и атрибутов

DOMPurify позволяет тонко настраивать список разрешённых тегов и атрибутов. Например:

const safeHTML = DOMPurify.sanitize(unsafeHTML, {
  ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'ul', 'ol', 'li', 'code', 'pre'],
  ALLOWED_ATTR: ['href', 'title', 'target', 'rel']
});

Это исключает любые нестандартные или потенциально опасные элементы.

Контроль пользовательского ввода

  • Ограничение длины Markdown-контента предотвращает атаки через огромные блоки HTML или скрипты.
  • Проверка ссылок на наличие схем jav * ascript: или других небезопасных протоколов.
  • Использование регулярных выражений или специализированных библиотек для валидации контента перед рендерингом.

Работа с плагинами

Markdown-it поддерживает плагины, но они могут быть источником уязвимостей. Рекомендации:

  • Использовать только проверенные плагины из официальных источников.
  • Изучать исходный код плагина перед подключением.
  • Ограничивать плагины только теми функциями, которые необходимы для проекта.

Логи и мониторинг

Для защиты системы важно вести логирование обработанных Markdown-документов и мониторить аномальные или потенциально вредоносные паттерны:

  • Часто встречающиеся <script> или <iframe> в пользовательском контенте.
  • Необычные URL-ссылки.
  • Скрытые теги или инъекции атрибутов.

Вывод

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

Эти практики создают безопасную среду при работе с Markdown, минимизируя возможность эксплуатации уязвимостей через пользовательский ввод.