Markdown-it предоставляет мощный движок для парсинга Markdown в JavaScript, однако при работе с пользовательским вводом возникает риск внедрения небезопасного контента, особенно HTML, JavaScript и URL. Для безопасной обработки необходимо использовать встроенные механизмы и сторонние плагины.
1. html
Опция html позволяет управлять поддержкой HTML-тегов
внутри Markdown. По умолчанию она отключена:
const md = require('markdown-it')({
html: false
});
html: true — разрешает интерпретацию HTML-тегов.html: false — все HTML-теги экранируются и выводятся
как текст.Для предотвращения XSS (Cross-Site Scripting) рекомендуется держать
html: false, если входные данные приходят от
пользователей.
2. linkify
Опция linkify автоматически преобразует URL и email в
ссылки:
const md = require('markdown-it')({
linkify: true
});
Важно понимать, что автоматическое создание ссылок может быть
источником уязвимостей через jav * ascript: URL. Для защиты
следует использовать функцию-проверку URL:
const md = require('markdown-it')({
linkify: true,
linkTarget: '_blank'
});
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 hrefIndex = tokens[idx].attrIndex('href');
if (hrefIndex >= 0) {
const url = tokens[idx].attrs[hrefIndex][1];
if (url.startsWith('jav * ascript:')) {
tokens[idx].attrs[hrefIndex][1] = '#';
}
}
return defaultRender(tokens, idx, options, env, self);
};
1. DOMPurify
Для безопасного рендеринга HTML-тегов используется библиотека DOMPurify:
const DOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const window = (new JSDOM('')).window;
const purify = DOMPurify(window);
const unsafeHtml = md.render(userInput);
const safeHtml = purify.sanitize(unsafeHtml);
2. sanitize-html
Альтернатива DOMPurify — sanitize-html, позволяющая более детально контролировать разрешённые теги и атрибуты:
const sanitizeHtml = require('sanitize-html');
const safeHtml = sanitizeHtml(md.render(userInput), {
allowedTags: ['b', 'i', 'em', 'strong', 'a', 'p', 'ul', 'li', 'ol'],
allowedAttributes: {
'a': ['href', 'name', 'target']
},
allowedSchemes: ['http','https','mailto']
});
allowedSchemes предотвращает использование опасных схем
вроде jav * ascript:.allowedAttributes ограничивает атрибуты, которые могут
быть внедрены.Markdown-it позволяет кастомизировать рендеринг через renderer rules:
md.renderer.rules.image = function (tokens, idx, options, env, self) {
const token = tokens[idx];
const srcIndex = token.attrIndex('src');
if (srcIndex >= 0) {
const src = token.attrs[srcIndex][1];
if (!src.startsWith('https://') && !src.startsWith('http://')) {
token.attrs[srcIndex][1] = '';
}
}
return self.renderToken(tokens, idx, options);
};
DOMPurify для полной защиты.При обработке Markdown от пользователей рекомендуется придерживаться многоуровневой стратегии безопасности:
html: false).DOMPurify или sanitize-html) для финального
HTML.const MarkdownIt = require('markdown-it');
const DOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const window = (new JSDOM('')).window;
const purify = DOMPurify(window);
const md = new MarkdownIt({
html: false,
linkify: true
});
// Фильтрация ссылок
const defaultLinkRender = 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 hrefIndex = tokens[idx].attrIndex('href');
if (hrefIndex >= 0) {
const url = tokens[idx].attrs[hrefIndex][1];
if (!url.startsWith('http://') && !url.startsWith('https://') && !url.startsWith('mailto:')) {
tokens[idx].attrs[hrefIndex][1] = '#';
}
}
return defaultLinkRender(tokens, idx, options, env, self);
};
// Рендер Markdown в безопасный HTML
function renderSafeMarkdown(input) {
const rawHtml = md.render(input);
return purify.sanitize(rawHtml);
}
Эта конфигурация гарантирует, что любой пользовательский Markdown будет преобразован в безопасный HTML без риска XSS и других атак через внедрение скриптов.