В контексте работы с Markdown и HTML в экосистеме JavaScript библиотеки Remark и Rehype предоставляют гибкие инструменты для обработки и трансформации контента. Одной из ключевых задач при работе с внешними данными является санитизация URL и протоколов, чтобы предотвратить внедрение вредоносного кода и обеспечить безопасность рендеринга.
При обработке Markdown и HTML URL могут содержать:
jav * ascript:.Целью санитизации является фильтрация или нормализация
URL, оставляя только безопасные протоколы (http,
https, mailto, tel) и корректные
форматы ссылок.
Remark работает с Markdown AST (Abstract Syntax
Tree) и предоставляет доступ к узлам типа link и
image, где хранятся URL. Для санитизации можно использовать
плагинную архитектуру:
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkStringify from 'remark-stringify';
import { visit } from 'unist-util-visit';
function sanitizeUrls() {
return (tree) => {
visit(tree, ['link', 'image'], (node) => {
const allowedProtocols = ['http:', 'https:', 'mailto:', 'tel:'];
try {
const url = new URL(node.url, 'http://example.com');
if (!allowedProtocols.includes(url.protocol)) {
node.url = '#';
}
} catch {
node.url = '#';
}
});
};
}
const processor = unified()
.use(remarkParse)
.use(sanitizeUrls)
.use(remarkStringify);
const markdown = '[Click me](jav * ascript:alert("XSS"))';
processor.process(markdown).then((file) => {
console.log(String(file));
});
Ключевые моменты:
unist-util-visit для обхода всех ссылок и
изображений.URL конструктор обеспечивает корректную проверку
протокола.#).Rehype работает с HTML AST и позволяет выполнять
аналогичную санитизацию на уровне тегов a и
img:
import { unified } from 'unified';
import rehypeParse from 'rehype-parse';
import rehypeStringify from 'rehype-stringify';
import { visit } from 'unist-util-visit';
function sanitizeUrlsRehype() {
return (tree) => {
visit(tree, 'element', (node) => {
if (node.tagName === 'a' || node.tagName === 'img') {
const urlAttr = node.tagName === 'a' ? 'href' : 'src';
if (node.properties && node.properties[urlAttr]) {
const allowedProtocols = ['http:', 'https:', 'mailto:', 'tel:'];
try {
const url = new URL(node.properties[urlAttr], 'http://example.com');
if (!allowedProtocols.includes(url.protocol)) {
node.properties[urlAttr] = '#';
}
} catch {
node.properties[urlAttr] = '#';
}
}
}
});
};
}
const html = '<a href="jav * ascript:alert(1)">Click me</a>';
unified()
.use(rehypeParse, { fragment: true })
.use(sanitizeUrlsRehype)
.use(rehypeStringify)
.process(html)
.then((file) => {
console.log(String(file));
});
Особенности:
element, проверяя
tagName.URL.sanitize-html, интегрируя их в pipeline Remark/Rehype.Сочетание AST-обхода и строгой фильтрации протоколов обеспечивает надежную защиту контента, позволяя безопасно рендерить Markdown и HTML с внешних источников.