❯ Поддержка браузеров
Ранняя версия Sanitizer API была реализована в Chrome, но позже была удалена. Поэтому не стоит ориентироваться на старые материалы — спецификация API со временем значительно изменилась.
На данный момент Sanitizer API поддерживается в Firefox Nightly в соответствии с актуальной спецификацией. В Chrome Canary он также доступен, но только при включении специального флага. В Safari реализация пока не ведется, однако команда разработчиков Safari выразила поддержку этой инициативе.
Trusted Types API уже реализован в Chrome/Edge, Samsung Internet, Safari и Firefox Nightly. В Chrome он поддерживается начиная с версии 83, а начиная с версии 144 полностью соответствует последней спецификации и реализации в других браузерах.
Метод setHTMLUnsafe() поддерживается во всех браузерах.
❯ setHTML()
Метод setHTML() безопасно добавляет HTML в DOM, защищая страницу от атак типа XSS (межсайтового скриптинга):
const div = document.querySelector('div');
div.setHTML(html);
Метод setHTML() всегда удаляет встроенные обработчики событий, а также следующие элементы HTML:
scriptembedframeiframeobjectэлемент SVG
use
По умолчанию он также фильтрует гораздо большее количество элементов и атрибутов:
style, link, img, video, button, form, input, textarea, label, select, option, output, details, summary, template, все кастомные элементы (например, <wa-dropdown>), пользовательские атрибуты данных (data-), ARIA-атрибуты, встроенные стили и HTML-комментарии (список неполный, и в будущем он может измениться).
Рассмотрим следующий код:
const html =
`<h1 onclick="console.log('hi')">testing</h1>
<button>Click me</button>
<img src="cat.jpg">
<iframe src="https://olliewilliams.xyz/">`;
const div = document.querySelector('div');
div.setHTML(html);
Содержимое div будет следующим:
<h1>testing</h1>
❯ Настройка удаления элементов и атрибутов
С помощью кастомного санитайзера можно управлять тем, какие элементы и атрибуты будут удаляться. При самом щадящем подходе приведенный ниже код удаляет только элементы embed, frame, iframe, object, script и use, а также встроенные обработчики событий:
div.setHTML(html, { sanitizer: {} });
Белый список
При конфигурации с белым списком (allowlist) задаются только разрешенные элементы и атрибуты. Все, что в список не входит, автоматически удаляется:
const mySanitizer = new Sanitizer({
elements: ["h1"],
attributes: ["style"]
});
div.setHTML(html, { sanitizer: mySanitizer });
В качестве альтернативы можно использовать объект:
div.setHTML(html, {
elements: ["h1"],
attributes: ["style"]
});
Для более точного контроля разрешенные атрибуты можно указывать для каждого элемента отдельно:
const sanitizer = new Sanitizer({
elements: [
{ name: "h1", attributes: [] },
{ name: "h2", attributes: ["style"] },
],
});
Приведенный выше санитайзер удалит все атрибуты у h1, но оставит атрибут style у h2.
Черный список
В качестве альтернативы санитайзер можно настроить по принципу черного списка, указывая элементы и атрибуты, которые должны быть удалены:
const sanitizer = new Sanitizer({
removeElements: ["a"],
removeAttributes: ["id"],
});
С указанным выше санитайзером вызов setHTML(html, { sanitizer: sanitizer }) удалит только элементы a, script, embed, frame, iframe, object и use, а также атрибуты id и встроенные обработчики событий.
❯ Trusted Types и Sanitizer API
Обе технологии преследуют одну цель — предотвращение межсайтового скриптинга (XSS). Они дополняют друг друга: Sanitizer API позволяет безопасно создавать DOM-деревья, а Trusted Types гарантирует, что в небезопасные места DOM будет попадать только проверенный контент. Это обеспечивает защиту кода от непреднамеренного внесения XSS-уязвимостей разработчиками.
Доверенные типы (Trusted Types) включаются через заголовок Content-Security-Policy (CSP):
Content-Security-Policy: trusted-types passthrough legacysanitize; require-trusted-types-for 'script';
Другие директивы, не относящиеся к Trusted Types, обычно тоже включаются в заголовок CSP, но в приведенном примере показан минимальный вариант. passthrough и legacysanitize — это имена политик, которые создаются на клиенте; политики могут носить любые имена и создаваться в любом количестве. Этот список ограничивает, какие имена политик могут использоваться в клиентском коде.
Если вышеуказанный заголовок установлен, любая попытка передать строку в небезопасный приемник (unsafe sink) приведет к возникновению ошибки TypeError, например:
div1.setHTMLUnsafe(`<iframe src='https://olliewilliams.xyz/'/>`); // TypeError
div2.innerHTML = "<h2>Hello world</h2>"; // TypeError
В консоли инструментов разработчика в браузере появится ошибка:

Небезопасный приемник
Современные методы:
setHTMLUnsafe()Document.parseHTMLUnsafe()
Старые свойства и методы:
innerHTMLouterHTMLparseFromString()document.writedocument.writelnПрисвоение значения свойству
srcdocэлементаiframeчерез JS
При включенном заголовке Trusted Types эти методы можно использовать, но не со строками. Вместо строк используются объекты TrustedHTML.
Создание политики
Объект TrustedHTML возвращается методом createHTML(). То, как будет преобразована строка, определяется самим разработчиком — API Trusted Types сам по себе не выполняет очистку строки и не делает ее безопаснее. В этом примере входной HTML возвращается без изменений, но уже как объект TrustedHTML, а не как обычная строка:
const passThroughPolicy = trustedTypes.createPolicy("passthrough", {
createHTML: (input) => input
});
const unsanitizedHTML = passThroughPolicy.createHTML("<iframe src='https://olliewilliams.xyz/'/>");
Приведенный код по сути означает "Я доверяю этому HTML". Очевидно, что это не лучшая практика для общей политики. Передача такого неочищенного HTML в innerHTML или любой другой приемник не вызовет ошибку, но он не будет безопаснее обычной строки. В отдельных случаях может потребоваться вставка HTML без очистки (для этого существует метод setHTMLUnsafe()), например, при получении HTML с сервера, содержащего декларативный теневой DOM:
const target = document.getElementById('target');
fetch('/hopefullyverytrustworthy')
.then(response => response.text())
.then((html) => {
const unsanitizedHTML = passThroughPolicy.createHTML(html);
target.setHTMLUnsafe(unsanitizedHTML);
});
Как правило, метод createHTML() применяется для очистки строки. Поскольку поддержка Trusted Types в браузерах шире, чем у Sanitizer API, политика обычно выполняет очистку с помощью открытых библиотек, например DOMPurify.
window.trustedTypes.createPolicy('legacysanitize', {
createHTML: (input) =>
DOMPurify.sanitize(input, { RETURN_TRUSTED_TYPE: false }),
});
RETURN_TRUSTED_TYPE: false является обязательным, потому что DOMPurify.sanitize() по умолчанию возвращает объект TrustedHTML, а createHTML() ожидает строку.
Когда поддержка Sanitizer API в браузерах станет шире, необходимость в сторонних инструментах, вроде DOMPurify, отпадет. В большинстве случаев шаблон с createHTML() окажется излишним — достаточно будет использовать setHTML(). Методы setHTML() и Document.parseHTML() не относятся к небезопасным приемникам и не вызывают ошибки при включенных Trusted Types, поскольку им передаются обычные строки, а не объекты TrustedHTML.
По возможности рекомендуется:
использовать
setHTML()вместоinnerHTMLиsetHTMLUnsafe()использовать
Document.parseHTML()вместоparseFromString()илиDocument.parseHTMLUnsafe()
Однако бывают случаи, когда setHTML() использовать нельзя — например, при установке свойства srcdoc элемента iframe.
Эта функциональность еще довольно новая. При обнаружении ошибок и неточностей пишите в Bluesky, Twitter или Mastodon.
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud - в нашем Telegram-канале ↩