При работе с HTML-контентом, особенно когда он поступает из внешних источников, крайне важно контролировать, какие теги и атрибуты допустимы. Это предотвращает внедрение небезопасного кода и обеспечивает корректное отображение контента. В экосистеме JavaScript для работы с Markdown и HTML, библиотеки Remark и Rehype предоставляют мощные средства для реализации белых списков тегов и атрибутов.
Белый список тегов (allowed tags) — это перечень
HTML-тегов, которые разрешено оставлять в контенте. Все остальные теги
автоматически удаляются или экранируются. Например, разрешить
<p>, <a>,
<strong>, <em>, и запретить
<script>, <iframe>.
Белый список атрибутов (allowed attributes) — это
набор разрешённых атрибутов для конкретных тегов. Даже разрешённый тег
может быть опасным, если его атрибуты содержат вредоносный код.
Например, у <a> разрешён атрибут href,
но запрещён onclick.
Для фильтрации HTML чаще всего используют rehype-sanitize. Он позволяет задавать конфигурацию, которая определяет допустимые теги и атрибуты.
Пример базовой конфигурации:
import { unified } from 'unified';
import rehypeParse from 'rehype-parse';
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
import rehypeStringify from 'rehype-stringify';
const schema = {
...defaultSchema,
tagNames: ['p', 'a', 'strong', 'em', 'ul', 'li'],
attributes: {
a: ['href', 'title'],
p: [],
li: [],
strong: [],
em: []
}
};
const processor = unified()
.use(rehypeParse, { fragment: true })
.use(rehypeSanitize, schema)
.use(rehypeStringify);
const html = '<p oncl ick="alert(1)">Текст <a href="https://example.com">ссылка</a></p>';
const sanitized = processor.processSync(html).toString();
console.log(sanitized);
В этом примере:
<p> сохраняется, но атрибут onclick
удаляется.<a> сохраняется только с разрешённым атрибутом
href.Иногда необходимо разрешить нестандартные или кастомные теги. Для
этого достаточно добавить их в tagNames и задать допустимые
атрибуты:
schema.tagNames.push('custom-widget');
schema.attributes['custom-widget'] = ['data-id', 'class'];
Теперь
<custom-widget data-id="123" class="highlight"></custom-widget>
пройдёт через фильтр без изменений.
Remark используется для парсинга Markdown, который затем преобразуется в HTML через Rehype. Для работы с белыми списками применяют remark-rehype с последующей обработкой через rehype-sanitize:
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeSanitize from 'rehype-sanitize';
import rehypeStringify from 'rehype-stringify';
const markdownProcessor = unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeSanitize, schema)
.use(rehypeStringify);
const md = 'Текст с [ссылкой](https://example.com) и <script>alert(1)</script>';
const htmlOutput = markdownProcessor.processSync(md).toString();
console.log(htmlOutput);
Здесь происходит последовательное:
<a> и изображений
<img> проверять, чтобы атрибуты href и
src не содержали потенциально опасные схемы
(jav * ascript: и пр.).Белые списки тегов и атрибутов позволяют не только защитить приложение от XSS-атак, но и контролировать структуру выходного HTML, обеспечивая чистоту и предсказуемость контента.