Интеграция с sanitize-html

Для начала необходимо установить библиотеку marked и библиотеку sanitize-html, которая будет отвечать за безопасную обработку HTML:

npm install marked sanitize-html

После установки подключение библиотек в проекте на Node.js выполняется следующим образом:

const marked = require('marked');
const sanitizeHtml = require('sanitize-html');

В случае использования ES-модулей:

import { marked } from 'marked';
import sanitizeHtml from 'sanitize-html';

Основной принцип интеграции

marked преобразует Markdown в HTML. Проблема безопасности возникает из-за возможности внедрения вредоносного HTML или скриптов. Для предотвращения XSS-атак используется sanitize-html, который очищает результат, оставляя только разрешённые теги и атрибуты.

Типовой поток обработки выглядит так:

  1. Markdown-текст → marked → HTML.
  2. HTML → sanitize-html → безопасный HTML.
const markdown = `
# Пример заголовка

Текст с [ссылкой](https://example.com) и **жирным текстом**.

<script>alert('Опасно');</script>
`;

const html = marked(markdown);

const cleanHtml = sanitizeHtml(html, {
  allowedTags: sanitizeHtml.defaults.allowedTags.concat(['h1', 'h2']),
  allowedAttributes: {
    a: ['href', 'name', 'target']
  }
});

console.log(cleanHtml);

В этом примере:

  • marked преобразует Markdown в HTML.
  • sanitizeHtml удаляет тег <script> и любые запрещённые атрибуты.
  • Разрешены заголовки <h1>, <h2> и ссылки с атрибутами href, name, target.

Настройка параметров sanitize-html

sanitize-html предоставляет гибкую настройку:

  • allowedTags – список разрешённых HTML-тегов.
  • allowedAttributes – разрешённые атрибуты для каждого тега.
  • allowedSchemes – допустимые схемы для ссылок (http, https, mailto).
  • transformTags – возможность модифицировать теги при очистке.

Пример расширенной настройки:

const cleanHtml = sanitizeHtml(html, {
  allowedTags: ['b', 'i', 'em', 'strong', 'a', 'p', 'ul', 'li', 'h1', 'h2', 'h3'],
  allowedAttributes: {
    a: ['href', 'target', 'rel']
  },
  allowedSchemes: ['http', 'https', 'mailto'],
  transformTags: {
    'a': (tagName, attribs) => {
      attribs.rel = 'noopener noreferrer';
      return { tagName, attribs };
    }
  }
});

Здесь:

  • Все ссылки получают атрибуты rel="noopener noreferrer", что защищает от уязвимости tabnabbing.
  • Разрешены только безопасные HTML-теги и атрибуты.

Интеграция с асинхронными потоками

Если Markdown поступает из внешних источников (например, API или файловой системы), безопасная обработка может быть встроена в асинхронный поток данных:

import fs from 'fs/promises';

async function renderMarkdownFile(filePath) {
  const markdown = await fs.readFile(filePath, 'utf-8');
  const html = marked(markdown);
  return sanitizeHtml(html, {
    allowedTags: sanitizeHtml.defaults.allowedTags,
    allowedAttributes: {
      a: ['href', 'target']
    }
  });
}

renderMarkdownFile('example.md').then(cleanHtml => console.log(cleanHtml));

Такой подход позволяет безопасно рендерить Markdown из файлов или запросов пользователей без риска внедрения вредоносного кода.

Дополнительные меры безопасности

  • Ограничение вложенных тегов: иногда полезно запрещать теги вроде <iframe> или <object>, которые могут внедрять внешние ресурсы.
  • Использование Content Security Policy (CSP): помимо sanitize-html, настройка CSP в заголовках HTTP повышает уровень безопасности.
  • Регулярное обновление библиотек: библиотеки безопасности часто исправляют новые уязвимости, поэтому важно поддерживать их актуальными.

Примеры практического применения

  1. Система комментариев: Markdown, вводимый пользователями, преобразуется в HTML через marked и очищается через sanitize-html.
  2. Блог-платформа: публикации авторов автоматически конвертируются и безопасно выводятся на странице.
  3. Документация проекта: Markdown-файлы документации можно безопасно рендерить на сайте без риска XSS.

Поддержка пользовательских рендереров в marked

marked позволяет настраивать собственный рендерер, что позволяет дополнительно фильтровать или модифицировать элементы перед передачей в sanitize-html:

const renderer = {
  link(href, title, text) {
    // Все ссылки открываются в новом окне
    return `<a href="${href}" target="_blank" rel="noopener noreferrer">${text}</a>`;
  }
};

const html = marked(markdown, { renderer });
const cleanHtml = sanitizeHtml(html);

Такой подход объединяет преимущества пользовательского контроля рендеринга и надёжной очистки HTML.

Интеграция marked и sanitize-html обеспечивает безопасное отображение Markdown, сохраняя при этом функциональность ссылок, форматирования текста и заголовков.