
DOMPurify v3.4.15
DOMPurify — это DOM-only, сверхбыстрый, сверхтолерантный XSS-санитайзер для HTML, MathML и SVG. DOMPurify работает с безопасным набором настроек по умолчанию, но предлагает множество возможностей конфигурации и хуков. Демо:
DOMPurify
DOMPurify — это сверхбыстрый, чрезвычайно устойчивый XSS-санитайзер для HTML, MathML и SVG, работающий исключительно с DOM.
Он также очень прост в использовании и освоении. DOMPurify был запущен в феврале 2014 года и с тех пор достиг версии v3.4.15.
DOMPurify работает как JavaScript и поддерживается во всех современных браузерах (Safari (10+), Opera (15+), Edge, Firefox и Chrome — а также практически во всём остальном, что использует Blink, Gecko или WebKit). Он не ломается в MSIE или других устаревших браузерах. Он просто ничего не делает.
Обратите внимание, что DOMPurify v2.5.9 — последняя версия с поддержкой MSIE. Для важных обновлений безопасности, совместимых с MSIE, используйте ветку 2.x.
Наши автоматизированные тесты охватывают 9 комбинаций браузер/ОС на текущих движках (Chromium, Firefox и WebKit в Ubuntu, macOS и Windows) при каждом push, а отдельная матрица повторно прогоняет набор тестов на снимках старых движков (примерно до Chromium 110, Firefox 108 и WebKit 16.4, возрастом около трёх лет), чтобы регрессии в устаревших браузерах тоже выявлялись. Мы также запускаем Node.js v20, v22, v24, v25 и v26 с DOMPurify на jsdom. Более старые версии Node, как известно, тоже работают, но, э-э... без гарантий.
DOMPurify написан специалистами по безопасности, обладающими обширным опытом в веб-атаках и XSS. Не бойтесь. Для получения дополнительной информации также прочитайте о наших Целях безопасности и модели угроз. Пожалуйста, прочитайте. Серьёзно. А если вам нравятся кровавые подробности, страница Классы атак и история обходов каталогизирует приёмы мутации парсера, пространств имён, перезаписи (clobbering) и шаблонов, от которых защищается DOMPurify.
Проект DOMPurify вдохновил создание HTML Sanitizer API, который уже поставляется во многих браузерах. Та же возможность сейчас стандартизируется непосредственно в спецификации WHATWG HTML.
Содержание
- Что он делает?
- Как его использовать?
- Есть ли демо?
- Что делать, если я найду уязвимость?
- Можно несколько примеров очистки?
- Что поддерживается?
- А как насчёт устаревших браузеров вроде Internet Explorer?
- А как насчёт DOMPurify и Trusted Types?
- Можно ли настроить DOMPurify?
- Постоянная конфигурация
- Хуки
- Удалённая конфигурация
- Непрерывная интеграция
- Список рассылки по безопасности
- Кто внёс вклад?
Что он делает?
DOMPurify очищает HTML и предотвращает XSS-атаки. Вы можете передать DOMPurify, например, строку, полную грязного HTML, и он вернёт строку (если не настроено иное) с чистым HTML. DOMPurify удалит всё, что содержит опасный HTML, и тем самым предотвратит XSS-атаки и другие неприятности. Он также чертовски быстр. Мы используем технологии, предоставляемые браузером, и превращаем их в XSS-фильтр. Чем быстрее ваш браузер, тем быстрее будет DOMPurify.
Как его использовать?
Это просто. Просто подключите DOMPurify на своём сайте.
Использование неминифицированной версии (доступна source-map)```html
### Использование минифицированной и протестированной production-версии (доступна source-map)```html
<script type="text/javascript" src="dist/purify.min.js"></script>
После этого вы можете очищать строки, выполнив следующий код:```js const clean = DOMPurify.sanitize(dirty);
Или, возможно, это, если вы любите работать с Angular или подобными:```js
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize('<b>hello there</b>');
Полученный HTML можно записать в DOM-элемент с помощью innerHTML или в DOM с помощью document.write(). Это полностью на ваше усмотрение.
Обратите внимание, что по умолчанию мы разрешаем HTML, SVG и MathML. Если вам нужен только HTML, что может быть очень распространённым случаем использования, вы также можете легко это настроить:```js
const clean = DOMPurify.sanitize(dirty, { USE_PROFILES: { html: true } });
### Есть ли здесь подводные камни?
Что ж, обратите внимание: если вы _сначала_ санируете HTML, а _затем_ изменяете его, вы можете легко **свести на нет эффект санитизации**. Если вы передаёте санированную разметку в другую библиотеку _после_ санитизации, убедитесь, что эта библиотека не манипулирует HTML самостоятельно. См. [Security Goals & Threat Model](https://github.com/cure53/DOMPurify/wiki/Security-Goals-&-Threat-Model) для рецептов безопасного использования и тегов/атрибутов, о которых стоит подумать дважды, а также [Attack Classes & Bypass History](https://github.com/cure53/DOMPurify/wiki/Attack-Classes-&-Bypass-History) о том, почему постобработка и изменение контекста разметки сводят санитизацию на нет.
### Хорошо, логично, двигаемся дальше
После санитизации разметки вы также можете взглянуть на свойство `DOMPurify.removed` и узнать, какие элементы и атрибуты были удалены. Пожалуйста, **не используйте** это свойство для принятия каких-либо решений, критичных для безопасности. Это всего лишь небольшой помощник для любопытных умов.
### Запуск DOMPurify на сервере
Технически DOMPurify также работает на стороне сервера с Node.js. Наша поддержка стремится следовать [циклу релизов Node.js](https://nodejs.org/en/about/previous-releases).
Для запуска DOMPurify на сервере требуется наличие DOM, что, вероятно, никого не удивит. Обычно [jsdom](https://github.com/jsdom/jsdom) является инструментом выбора, и мы **настоятельно рекомендуем** использовать последнюю версию _jsdom_.
Почему? Потому что старые версии _jsdom_ известны своими багами, которые приводят к XSS _даже если_ DOMPurify делает всё на 100% правильно. Существуют **известные векторы атак**, например, в _jsdom v19.0.0_, которые исправлены в _jsdom v20.0.0_ — и поэтому мы действительно рекомендуем поддерживать _jsdom_ в актуальном состоянии.
Также учтите, что существуют такие инструменты, как [happy-dom](https://github.com/capricorn86/happy-dom), но на данный момент они **не считаются безопасными**. Комбинировать DOMPurify с _happy-dom_ в настоящее время не рекомендуется, и это, скорее всего, приведёт к XSS. Для понимания того, почему выбранный вами серверный DOM является частью вашей доверенной вычислительной базы, см. [Attack Classes & Bypass History](https://github.com/cure53/DOMPurify/wiki/Attack-Classes-&-Bypass-History).
Помимо этого, вы можете спокойно использовать DOMPurify на сервере. Вероятно. Это действительно зависит от _jsdom_ или любого другого DOM, который вы используете на стороне сервера. Если вас это устраивает, вот как заставить это работать:```bash
npm install dompurify
npm install jsdom
Для jsdom (пожалуйста, используйте актуальную версию) подойдёт следующее:```js const createDOMPurify = require('dompurify'); const { JSDOM } = require('jsdom');
const window = new JSDOM('').window; const DOMPurify = createDOMPurify(window); const clean = DOMPurify.sanitize('hello there');
Или даже так, если вы предпочитаете работать с импортами:```js
import { JSDOM } from 'jsdom';
import DOMPurify from 'dompurify';
const window = new JSDOM('').window;
const purify = DOMPurify(window);
const clean = purify.sanitize('<b>hello there</b>');
Если у вас возникли проблемы с настройкой в вашей конкретной конфигурации, обратите внимание на замечательный проект isomorphic-dompurify, который решает множество проблем, с которыми могут столкнуться пользователи.```bash npm install isomorphic-dompurify
| `-s` | Silent mode. Suppress all output except errors. |
| `-v` | Verbose mode. Show detailed progress information. |
| `-o <file>` | Write output to the specified file instead of stdout. |
| `-f <format>` | Specify output format: `json`, `csv`, or `text`. |
| `-t <threads>` | Number of concurrent threads to use. Default: `10`. |
| `-T <seconds>` | Connection timeout in seconds. Default: `30`. |
| `-r <retries>` | Number of retry attempts on failure. Default: `3`. |
| `-p <proxy>` | Use the specified proxy for all connections. |
| `-H <header>` | Add a custom HTTP header to all requests. |
| `-A <agent>` | Set a custom User-Agent string. |
| `-c <config>` | Load configuration from the specified file. |
| `-n` | Disable DNS resolution. |
| `-q` | Quiet mode. Only display results. |
| `-d` | Enable debug output. |
| `-h` | Display help message and exit. |
| `-V` | Display version information and exit. |
### Configuration File
The tool supports a configuration file in YAML format. By default, it looks for `config.yaml` in the current working directory. You can specify an alternative path using the `-c` flag.
```yaml
# Example configuration file
target: example.com
threads: 20
timeout: 60
retries: 5
output:
format: json
file: results.json
proxy: http://127.0.0.1:8080
headers:
X-Custom-Header: "value"
Authorization: "Bearer token"
Usage Examples
Basic scan against a single target:
./tool -t example.com
Scan multiple targets from a file with verbose output:
./tool -f targets.txt -v
Export results in JSON format to a file:
./tool -t example.com -o results.json -f json
Use a proxy and custom headers:
./tool -t example.com -p http://127.0.0.1:8080 -H "X-Custom: value"
Run with increased thread count and timeout:
./tool -t example.com -t 50 -T 120
Output Format
The tool produces output in three formats: text (default), json, and csv.
Text output is human-readable and suitable for terminal viewing:
[+] Target: example.com
[+] Status: 200 OK
[+] Server: nginx/1.18.0
[+] Detected: WordPress 5.8.1
JSON output is structured and suitable for programmatic processing:
{
"target": "example.com",
"status": 200,
"server": "nginx/1.18.0",
"detected": {
"cms": "WordPress",
"version": "5.8.1"
}
}
CSV output is tabular and suitable for spreadsheet import:
target,status,server,cms,version
example.com,200,nginx/1.18.0,WordPress,5.8.1
Exit Codes
| Code | Meaning |
|---|---|
0 | Success. Operation completed without errors. |
1 | General error. An unexpected condition occurred. |
2 | Invalid arguments. Check command-line options. |
3 | Network error. Unable to reach the target. |
4 | Authentication failure. |
5 | Permission denied. |
6 | Configuration file not found or invalid. |
Troubleshooting
Connection refused: Verify the target is reachable and the port is open. Check firewall rules and network connectivity.
Timeout errors: Increase the timeout value using the -T flag. Consider reducing the number of threads with -t if the target is rate-limiting.
SSL/TLS errors: Ensure the target's certificate is valid. Use -k to skip certificate verification (not recommended for production).
Permission denied: Run the tool with appropriate privileges or check file permissions on the output directory.
Configuration not loaded: Verify the configuration file path and syntax. Use -d for debug output to see which configuration file is being used.```js
import DOMPurify from 'isomorphic-dompurify';
const clean = DOMPurify.sanitize('hello');
## Есть ли демо?
Конечно, есть демо! [Поиграйте с DOMPurify](https://cure53.de/purify)
## Что делать, если я найду уязвимость?
Прежде всего, пожалуйста, немедленно свяжитесь с нами по [электронной почте](mailto:[email protected]), чтобы мы могли заняться исправлением. [PGP-ключ](https://keyserver.ubuntu.com/pks/lookup?op=vindex&search=0xC26C858090F70ADA)
Кроме того, вы, вероятно, имеете право на вознаграждение за найденную уязвимость! Замечательные люди из [Fastmail](https://www.fastmail.com/) используют DOMPurify для своих сервисов и добавили нашу библиотеку в свою программу bug bounty. Поэтому, если вы найдёте способ обойти или ослабить DOMPurify, пожалуйста, также ознакомьтесь с их сайтом и [информацией о bug bounty](https://www.fastmail.com/about/bugbounty/).
## Можно несколько примеров очистки?
Как выглядит очищенная разметка? Что ж, [демо](https://cure53.de/purify) показывает это для большого набора опасных элементов. Но давайте также покажем несколько примеров поменьше!```js
DOMPurify.sanitize(''); // becomes <img src="https://raw.githubusercontent.com/cure53/dompurify/main/x">
DOMPurify.sanitize('<svg><g/onload=alert(2)//<p>'); // becomes <svg><g></g></svg>
DOMPurify.sanitize('<p>abcdef</p>'); // becomes <p>abc</p>
DOMPurify.sanitize('<math><mi//xlink:href="data:x,<script>alert(4)</script>">'); // becomes <math><mi></mi></math>
DOMPurify.sanitize('<TABLE><tr><td>HELLO</tr></TABL>'); // becomes <table><tbody><tr><td>HELLO</td></tr></tbody></table>
DOMPurify.sanitize('<UL><li><A HREF=//google.com>click</UL>'); // becomes <ul><li><a href="//google.com">click</a></li></ul>
Это лишь малая часть. Полную таксономию классов атак, из которых взяты эти примеры — mutation XSS, namespace confusion, DOM clobbering, rawtext breakouts и другие — см. в Attack Classes & Bypass History.
Что поддерживается?
В настоящее время DOMPurify поддерживает HTML5, SVG и MathML. По умолчанию DOMPurify разрешает CSS и пользовательские data-атрибуты HTML. DOMPurify также поддерживает Shadow DOM — и рекурсивно санитизирует DOM-шаблоны. DOMPurify также позволяет санитизировать HTML для использования с API jQuery $() и elm.html() без каких-либо известных проблем. Точный набор элементов и атрибутов, разрешённых по умолчанию, см. на вики-странице Default TAGs & ATTRIBUTEs allow-list & blocklist.
А как насчёт устаревших браузеров вроде Internet Explorer?
DOMPurify не делает вообще ничего. Он просто возвращает ровно ту строку, которую вы ему передали. DOMPurify предоставляет свойство isSupported, которое сообщает, сможет ли он выполнить свою работу, чтобы вы могли придумать собственный план действий на случай непредвиденных обстоятельств.
А как насчёт DOMPurify и Trusted Types?
В версии 1.0.9 в DOMPurify была добавлена поддержка Trusted Types API (MDN). В версии 2.0.0 был добавлен флаг конфигурации для управления поведением DOMPurify в этом отношении.
Когда DOMPurify.sanitize используется в среде, где доступен Trusted Types API и для RETURN_TRUSTED_TYPE установлено значение true, он пытается вернуть значение TrustedHTML вместо строки (поведение для опций конфигурации RETURN_DOM и RETURN_DOM_FRAGMENT не меняется).
Обратите внимание, что для создания политики в trustedTypes с использованием DOMPurify требуется RETURN_TRUSTED_TYPE: false, так как createHTML ожидает обычную строку, а не TrustedHTML. Пример ниже демонстрирует это.```js
window.trustedTypes.createPolicy('default', {
createHTML: (to_escape) =>
DOMPurify.sanitize(to_escape, { RETURN_TRUSTED_TYPE: false }),
});
Когда `TRUSTED_TYPES_POLICY` не передан, DOMPurify пытается создать собственную внутреннюю политику Trusted Types с именем `dompurify`. Если ваша страница уже определяет собственную политику вместе со строгим CSP (например, `trusted-types my-organization`), которая не разрешает политику с именем `dompurify`, эта попытка блокируется браузером и регистрирует предупреждение `TrustedTypes policy dompurify could not be created.` вместе с нарушением CSP.
Чтобы DOMPurify не создавал свою внутреннюю резервную политику, передайте `TRUSTED_TYPES_POLICY: null`. Это правильный выбор, когда вы вызываете `DOMPurify.sanitize` изнутри `createHTML` вашей собственной политики, и это означает, что вам не нужно добавлять `dompurify` в список разрешённых `trusted-types` вашего CSP.```js
window.trustedTypes.createPolicy('my-organization', {
createHTML: (input) =>
DOMPurify.sanitize(input, { TRUSTED_TYPES_POLICY: null }),
});
Не передавайте свою собственную политику обёртки обратно в DOMPurify в качестве её TRUSTED_TYPES_POLICY (например, через DOMPurify.setConfig({ TRUSTED_TYPES_POLICY: myPolicy })), когда createHTML этой политики уже вызывает DOMPurify.sanitize. Это по определению циклическая зависимость — санитизация вызовет политику, которая санитизирует, снова вызывая DOMPurify — и DOMPurify выбросит описательный TypeError, чтобы предотвратить бесконечную рекурсию. Ваша собственная политика должна вызывать DOMPurify; DOMPurify не должна быть настроена на вызов вашей политики.
Если вы хотите, чтобы этот паттерн политики default применялся автоматически на всей странице — так, чтобы каждый HTML-сток был санитизирован, включая устаревший код, сторонние виджеты и тысячи присваиваний innerHTML, которые вы не можете легко найти или переписать — обратите внимание на DOMFortify. Он устанавливает именно такую политику Trusted Types default, поддерживаемую DOMPurify, и полностью отказывает в скриптовых стоках (eval, script.src, ...). Это намеренно отдельный проект: DOMPurify остаётся сфокусированным санитайзером, а DOMFortify обрабатывает уровень принудительного применения на уровне всего документа, который намеренно выходит за рамки DOMPurify.
Могу ли я настроить DOMPurify?
Да. Включённые значения конфигурации по умолчанию уже довольно хороши — но вы, конечно, можете их переопределить. Загляните в папку /demos, чтобы увидеть множество примеров того, как можно настроить DOMPurify.
Прежде чем расширять список разрешённого (ADD_TAGS, ADD_ATTR, CUSTOM_ELEMENT_HANDLING, …) или ослаблять значение по умолчанию, стоит просмотреть теги и атрибуты, о которых стоит подумать дважды — некоторые из них опасны неочевидным образом.
Общие настройки```js
// strip {{ ... }}, ${ ... } and <% ... %> to make output safe for template systems // be careful please, this mode is not recommended for production usage. // allowing template parsing in user-controlled HTML is not advised at all. // only use this mode if there is really no alternative. const clean = DOMPurify.sanitize(dirty, { SAFE_FOR_TEMPLATES: true });
// change how e.g. comments containing risky HTML characters are treated.
// be very careful, this setting should only be set to false if you really only handle
// HTML and nothing else, no SVG, MathML or the like.
// Otherwise, changing from true to false will lead to XSS in this or some other way.
const clean = DOMPurify.sanitize(dirty, { SAFE_FOR_XML: false });
### Управление нашими списками разрешений и блокировок```js
// allow only <b> elements, very strict
const clean = DOMPurify.sanitize(dirty, { ALLOWED_TAGS: ['b'] });
// allow only <b> and <q> with style attributes
const clean = DOMPurify.sanitize(dirty, {
ALLOWED_TAGS: ['b', 'q'],
ALLOWED_ATTR: ['style'],
});
// allow all safe HTML elements but neither SVG nor MathML
// note that the USE_PROFILES setting will override the ALLOWED_TAGS setting
// so don't use them together
const clean = DOMPurify.sanitize(dirty, { USE_PROFILES: { html: true } });
// allow all safe SVG elements and SVG Filters, no HTML or MathML
const clean = DOMPurify.sanitize(dirty, {
USE_PROFILES: { svg: true, svgFilters: true },
});
// allow all safe MathML elements and SVG, but no SVG Filters
const clean = DOMPurify.sanitize(dirty, {
USE_PROFILES: { mathMl: true, svg: true },
});
// change the default namespace from HTML to something different
const clean = DOMPurify.sanitize(dirty, {
NAMESPACE: 'http://www.w3.org/2000/svg',
});
// leave all safe HTML as it is and add <style> elements to block-list
const clean = DOMPurify.sanitize(dirty, { FORBID_TAGS: ['style'] });
// leave all safe HTML as it is and add style attributes to block-list
const clean = DOMPurify.sanitize(dirty, { FORBID_ATTR: ['style'] });
// extend the existing array of allowed tags and add <my-tag> to allow-list
const clean = DOMPurify.sanitize(dirty, { ADD_TAGS: ['my-tag'] });
// extend the existing array of allowed attributes and add my-attr to allow-list
const clean = DOMPurify.sanitize(dirty, { ADD_ATTR: ['my-attr'] });
// use functions to control which additional tags and attributes are allowed
const allowlist = {
one: ['attribute-one'],
two: ['attribute-two'],
};
const clean = DOMPurify.sanitize(
'<one attribute-one="1" attribute-two="2"></one><two attribute-one="1" attribute-two="2"></two>',
{
ADD_TAGS: (tagName) => {
return Object.keys(allowlist).includes(tagName);
},
ADD_ATTR: (attributeName, tagName) => {
return allowlist[tagName]?.includes(attributeName) || false;
},
}
); // <one attribute-one="1"></one><two attribute-two="2"></two>
// prohibit ARIA attributes, leave other safe HTML as is (default is true)
const clean = DOMPurify.sanitize(dirty, { ALLOW_ARIA_ATTR: false });
// prohibit HTML5 data attributes, leave other safe HTML as is (default is true)
const clean = DOMPurify.sanitize(dirty, { ALLOW_DATA_ATTR: false });
Управление поведением, связанным с пользовательскими элементами```js
// DOMPurify allows to define rules for Custom Elements. When using the CUSTOM_ELEMENT_HANDLING // literal, it is possible to define exactly what elements you wish to allow (by default, none are allowed). // // The same goes for their attributes. By default, the built-in or configured allow.list is used. // // You can use a RegExp literal to specify what is allowed or a predicate, examples for both can be seen below. // When using a predicate function for attributeNameCheck, it can optionally receive the tagName as a second parameter // for more granular control over which attributes are allowed for specific elements. // The default values are very restrictive to prevent accidental XSS bypasses. Handle with great care!
const clean = DOMPurify.sanitize( '
', { CUSTOM_ELEMENT_HANDLING: { tagNameCheck: null, // no custom elements are allowed attributeNameCheck: null, // default / standard attribute allow-list is used allowCustomizedBuiltInElements: false, // no customized built-ins allowed }, } ); //const clean = DOMPurify.sanitize( '
', { CUSTOM_ELEMENT_HANDLING: { tagNameCheck: /^foo-/, // allow all tags starting with "foo-" attributeNameCheck: /baz/, // allow all attributes containing "baz" allowCustomizedBuiltInElements: true, // customized built-ins are allowed }, } ); //const clean = DOMPurify.sanitize( '
', { CUSTOM_ELEMENT_HANDLING: { tagNameCheck: (tagName) => tagName.match(/^foo-/), // allow all tags starting with "foo-" attributeNameCheck: (attr) => attr.match(/baz/), // allow all containing "baz" allowCustomizedBuiltInElements: true, // allow customized built-ins }, } ); //// Example with attributeNameCheck receiving tagName as a second parameter const clean = DOMPurify.sanitize( '', { CUSTOM_ELEMENT_HANDLING: { tagNameCheck: (tagName) => tagName.match(/^element-(one|two)$/), attributeNameCheck: (attr, tagName) => { if (tagName === 'element-one') { return ['attribute-one'].includes(attr); } else if (tagName === 'element-two') { return ['attribute-two'].includes(attr); } else { return false; } }, allowCustomizedBuiltInElements: false, }, } ); //
### Управление поведением, связанным со значениями URI```js
// extend the existing array of elements that can use Data URIs
const clean = DOMPurify.sanitize(dirty, { ADD_DATA_URI_TAGS: ['a', 'area'] });
// extend the existing array of elements that are safe for URI-like values (be careful, XSS risk)
const clean = DOMPurify.sanitize(dirty, { ADD_URI_SAFE_ATTR: ['my-attr'] });
Управление допустимыми значениями атрибутов```js
// allow external protocol handlers in URL attributes (default is false, be careful, XSS risk) // by default only http, https, ftp, ftps, tel, mailto, callto, sms, cid, xmpp and matrix are allowed. const clean = DOMPurify.sanitize(dirty, { ALLOW_UNKNOWN_PROTOCOLS: true });
// allow specific protocol handlers in URL attributes via regex (default is false, be careful, XSS risk) // by default only (protocol-)relative URLs, http, https, ftp, ftps, tel, mailto, callto, sms, cid, xmpp and matrix are allowed. // Default RegExp: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.-]+(?:[^a-z+.-:]|$))/i; const clean = DOMPurify.sanitize(dirty, { ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.-]+(?:[^a-z+.-:]|$))/i, });
### Влияние на возвращаемый тип```js
// return a DOM HTMLBodyElement instead of an HTML string (default is false)
const clean = DOMPurify.sanitize(dirty, { RETURN_DOM: true });
// return a DOM DocumentFragment instead of an HTML string (default is false)
const clean = DOMPurify.sanitize(dirty, { RETURN_DOM_FRAGMENT: true });
// use the RETURN_TRUSTED_TYPE flag to turn on Trusted Types support if available
const clean = DOMPurify.sanitize(dirty, { RETURN_TRUSTED_TYPE: true }); // will return a TrustedHTML object instead of a string if possible
// use a provided Trusted Types policy
const clean = DOMPurify.sanitize(dirty, {
// supplied policy must define createHTML and createScriptURL
TRUSTED_TYPES_POLICY: trustedTypes.createPolicy('dompurify', {
createHTML(s) {
return s;
},
createScriptURL(s) {
return s;
},
}),
});
// opt out of DOMPurify's internal `dompurify` Trusted Types policy entirely
// (useful when your CSP `trusted-types` allowlist does not include `dompurify`)
const clean = DOMPurify.sanitize(dirty, { TRUSTED_TYPES_POLICY: null });
Влияйте на то, как мы выполняем санитизацию```js
// return entire document including tags (default is false) const clean = DOMPurify.sanitize(dirty, { WHOLE_DOCUMENT: true });
// disable DOM Clobbering protection on output (default is true, handle with care, minor XSS risks here) const clean = DOMPurify.sanitize(dirty, { SANITIZE_DOM: false });
// enforce strict DOM Clobbering protection via namespace isolation (default is false)
// when enabled, isolates the namespace of named properties (i.e., id and name attributes)
// from JS variables by prefixing them with the string user-content-
const clean = DOMPurify.sanitize(dirty, { SANITIZE_NAMED_PROPS: true });
// keep an element's content when the element is removed (default is true) const clean = DOMPurify.sanitize(dirty, { KEEP_CONTENT: false });
// glue elements like style, script or others to document.body and prevent unintuitive browser behavior in several edge-cases (default is false) const clean = DOMPurify.sanitize(dirty, { FORCE_BODY: true });
// remove all elements under
// extend the default FORBID_CONTENTS list to also remove elements under
// change the parser type so sanitized data is treated as XML and not as HTML, which is the default const clean = DOMPurify.sanitize(dirty, { PARSER_MEDIA_TYPE: 'application/xhtml+xml', });
### Влияние на места санитизации```js
// use the IN_PLACE mode to sanitize a node "in place", which is much faster depending on how you use DOMPurify
const dirty = document.createElement('a');
dirty.setAttribute('href', 'javascript:alert(1)');
const clean = DOMPurify.sanitize(dirty, { IN_PLACE: true }); // see https://github.com/cure53/DOMPurify/issues/288 for more info
Есть даже больше примеров здесь, показывающих, как можно запускать, настраивать и конфигурировать DOMPurify под свои нужды.
Постоянная конфигурация
Вместо того чтобы многократно передавать одну и ту же конфигурацию в DOMPurify.sanitize, вы можете использовать метод DOMPurify.setConfig. Ваша конфигурация будет сохраняться до следующего вызова DOMPurify.setConfig или до вызова DOMPurify.clearConfig для её сброса. Помните, что активная конфигурация может быть только одна, а значит, после её установки все дополнительные параметры конфигурации, переданные в DOMPurify.sanitize, игнорируются.
Хуки
DOMPurify позволяет расширять свою функциональность, прикрепляя одну или несколько функций с помощью метода DOMPurify.addHook к одному из следующих хуков:
beforeSanitizeElementsuponSanitizeElement(Без 's' - вызывается для каждого элемента)afterSanitizeElementsbeforeSanitizeAttributesuponSanitizeAttributeafterSanitizeAttributesbeforeSanitizeShadowDOMuponSanitizeShadowNodeafterSanitizeShadowDOM
Он передаёт в callback текущий обрабатываемый узел DOM, при необходимости литерал с проверенными данными узла и атрибутов, а также конфигурацию DOMPurify. Ознакомьтесь с демонстрацией хука MentalJS, чтобы увидеть, как этот API можно удобно использовать.
Пример:```js DOMPurify.addHook( 'uponSanitizeAttribute', function (currentNode, hookEvent, config) { // Do something with the current node // You can also mutate hookEvent for current node (i.e. set hookEvent.forceKeepAttr = true) // For other than 'uponSanitizeAttribute' hook types hookEvent equals to null } );
### Замечание о вызове `sanitize()` из хука
**`DOMPurify.sanitize()` не является реентерабельным.** Пожалуйста, не вызывайте его изнутри хука или из callback-функции конфигурации, такой как `CUSTOM_ELEMENT_HANDLING.tagNameCheck` или `attributeNameCheck`. Эти callback-функции выполняются в _середине_ активного прохода санитайзера.
Вложенный вызов `sanitize()` повторно считывает переданную ему конфигурацию и тем самым **заменяет конфигурацию, которую всё ещё использует внешний проход**. Остальная часть внешнего документа затем санитизируется с использованием конфигурации вложенного вызова вместо вашей. Поскольку вложенный вызов обычно выполняется с конфигурацией по умолчанию, строгий allow-list `ALLOWED_TAGS` может незаметно расшириться обратно до стандартного где-то в середине документа — без ошибок и без предупреждений.
Если вам нужно санитизировать вложенную разметку, например HTML-фрагмент, содержащийся внутри значения атрибута, у вас есть два безопасных варианта. Либо задайте конфигурацию один раз с помощью [`DOMPurify.setConfig`](#persistent-configuration) вместо передачи её при каждом вызове, поскольку постоянная конфигурация используется совместно вложенным вызовом и остаётся в силе на протяжении всего прохода; либо соберите фрагменты во время хука и санитизируйте их отдельным вызовом `sanitize()` _после_ возврата внешнего вызова.
## Удалённая конфигурация
| Опция | С версии | Примечание |
| --------------- | -------- | --------------------------- |
| SAFE_FOR_JQUERY | 2.1.0 | Замена не требуется. |
## Непрерывная интеграция
В настоящее время мы используем GitHub Actions в сочетании с Playwright. Это позволяет нам подтверждать при каждом коммите, что всё работает в соответствующих современных браузерах, а отдельный запланированный workflow и workflow при слиянии повторно запускают набор тестов на старых снимках движков, чтобы также выявлять поломки в устаревших браузерах. Ознакомьтесь с логами сборки здесь: https://github.com/cure53/DOMPurify/actions
Вы также можете запускать локальные тесты, выполнив `npm run test`.
Все соответствующие коммиты будут подписаны ключом `0x24BB6BF4` для дополнительной безопасности (начиная с 8 апреля 2016 года).
### Разработка и участие
#### Установка (`npm i`)
Мы официально поддерживаем `npm`. Workflow GitHub Actions настроен на установку зависимостей с помощью `npm`. При использовании устаревшей версии `npm` мы не можем полностью гарантировать версии устанавливаемых зависимостей, что может привести к непредвиденным проблемам.
#### Скрипты
Мы используем ESLint через `xo` как часть нашего pre-commit workflow, чтобы помочь обеспечить согласованность кода. Кроме того, мы используем [Prettier](https://github.com/prettier/prettier) для форматирования исходного кода и Markdown, а ресурсы в `/dist` собираются через `rollup`.
Вот наши npm-скрипты:
- `npm run dev` — собрать неминифицированный UMD-бандл, отслеживая изменения в исходниках
- `npm run test` — проверить исходники линтером, запустить тесты через jsdom и запустить браузерные тесты в Chromium через Playwright
- `npm run test:jsdom` — запустить только тесты через jsdom
- `npm run test:happydom` — запустить набор тестов через happy-dom (неподдерживаемая среда; сохранена как проверка устойчивости, а не обещание совместимости)
- `npm run test:browser` — запустить только тесты через Playwright
- `npm run test:browser:legacy` — запустить набор тестов на старых движках браузеров (укажите `PW_MODULE` на закреплённую старую установку Playwright; см. `.github/workflows/legacy-browsers.yml`)
- `npm run test:ci` — запустить CI-поток тестирования для jsdom и Playwright
- `npm run test:fuzz` — запустить небольшой фаззер, покрывающий `sanitize()` и CONFIG
- `npm run bench` — запустить микробенчмарк jsdom на собранном `dist/purify.cjs` (сначала соберите; `--json` и `--compare a.json b.json` поддерживают A/B-запуски между ветками — результаты ориентировочные, подтверждайте заявления о пользовательском опыте в реальных браузерах)
- `npm run coverage` — собрать инструментированный бандл, запустить набор тестов jsdom и записать локальный HTML-отчёт о покрытии строк/ветвей в `coverage/index.html` (только в области jsdom, не запускается в CI)
- `npm run build:cov` — собрать только инструментированный бандл покрытия
- `npm run lint` — проверить исходники с помощью ESLint через xo
- `npm run format` — отформатировать исходники JavaScript/TypeScript и Markdown с помощью Prettier
- `npm run format:js` — отформатировать только исходники JavaScript/TypeScript
- `npm run format:md` — отформатировать только файлы Markdown
- `npm run build` — собрать объявления типов и дистрибутивные бандлы, затем исправить и очистить сгенерированные типы
- `npm run build:types` — только сгенерировать файлы объявлений TypeScript
- `npm run build:rollup` — собрать все бандлы Rollup
- `npm run build:umd` — собрать только неминифицированный UMD-бандл
- `npm run build:umd:min` — собрать только минифицированный UMD-бандл
- `npm run build:es` — собрать только бандл ES-модуля
- `npm run build:cjs` — собрать только бандл CommonJS
- `npm run build:fix-types` — постобработать сгенерированные файлы типов
- `npm run build:cleanup` — очистить временный сгенерированный вывод типов
- `npm run verify-typescript` — запустить скрипт верификации TypeScript
- `npm run commit-amend-build` — запустить вспомогательный скрипт мейнтейнера для изменения вывода сборки
Примечание: все скрипты запускаются через `npm run <script>`.
Есть и другие npm-скрипты, но они в основном предназначены для интеграции с CI или считаются «приватными», например для изменения дистрибутивных файлов сборки при каждом коммите.
## Список рассылки по безопасности
Мы поддерживаем список рассылки, который уведомляет о каждом выпуске DOMPurify, **критичном для безопасности**. Это означает, что если кто-то нашёл обход и мы исправили его выпуском (что всегда происходит при обнаружении обхода), на этот список будет отправлено письмо. Обычно это происходит в течение нескольких минут или нескольких часов после того, как мы узнаём об обходе. Подписаться на список можно здесь:
[https://lists.ruhr-uni-bochum.de/mailman/listinfo/dompurify-security](https://lists.ruhr-uni-bochum.de/mailman/listinfo/dompurify-security)
О выпусках с новыми функциями в этом списке не объявляется.
## Кто внёс вклад?
Многие люди помогли DOMPurify стать тем, чем он является сегодня, и они заслуживают признания!
[gnyselcuk](https://github.com/gnyselcuk), [leechristensen](https://github.com/leechristensen),[offset](https://github.com/offset), [Bankde](https://github.com/Bankde), [lukewarlow](https://github.com/lukewarlow), [DEMON1A](https://github.com/DEMON1A), [fg0x0](https://github.com/fg0x0), [kodareef5](https://github.com/kodareef5), [DavidOliver](https://github.com/DavidOliver), [1Jesper1](https://github.com/1Jesper1), [bencalif](https://github.com/bencalif), [trace37labs](https://github.com/trace37labs), [eddieran](https://github.com/eddieran), [christos-eth](https://github.com/christos-eth), [researchatfluidattacks](https://github.com/researchatfluidattacks), [frevadiscor](https://github.com/frevadiscor), [Rotzbua](https://github.com/Rotzbua), [binhpv](https://github.com/binhpv), [MariusRumpf](https://github.com/MariusRumpf), [prasadrajandran](https://github.com/prasadrajandran), [Cybozu 💛💸](https://github.com/cybozu), [hata6502 💸](https://github.com/hata6502), [openclaw 💸](https://github.com/openclaw), [intra-mart-dh 💸](https://github.com/intra-mart-dh), [nelstrom ❤️](https://github.com/nelstrom), [hash_kitten ❤️](https://twitter.com/hash_kitten), [kevin_mizu ❤️](https://twitter.com/kevin_mizu), [icesfont ❤️](https://github.com/icesfont), [reduckted ❤️](https://github.com/reduckted), [dcramer 💸](https://github.com/dcramer), [JGraph 💸](https://github.com/jgraph), [baekilda 💸](https://github.com/baekilda), [Healthchecks 💸](https://github.com/healthchecks), [Sentry 💸](https://github.com/getsentry), [jarrodldavis 💸](https://github.com/jarrodldavis), [CynegeticIO](https://github.com/CynegeticIO), [ssi02014 ❤️](https://github.com/ssi02014), [GrantGryczan](https://github.com/GrantGryczan), [Lowdefy](https://twitter.com/lowdefy), [granlem](https://twitter.com/MaximeVeit), [oreoshake](https://github.com/oreoshake), [tdeekens ❤️](https://github.com/tdeekens), [peernohell ❤️](https://github.com/peernohell), [is2ei](https://github.com/is2ei), [SoheilKhodayari](https://github.com/SoheilKhodayari), [franktopel](https://github.com/franktopel), [NateScarlet](https://github.com/NateScarlet), [neilj](https://github.com/neilj), [fhemberger](https://github.com/fhemberger), [Joris-van-der-Wel](https://github.com/Joris-van-der-Wel), [ydaniv](https://github.com/ydaniv), [terjanq](https://twitter.com/terjanq), [filedescriptor](https://github.com/filedescriptor), [ConradIrwin](https://github.com/ConradIrwin), [gibson042](https://github.com/gibson042), [choumx](https://github.com/choumx), [0xSobky](https://github.com/0xSobky), [styfle](https://github.com/styfle), [koto](https://github.com/koto), [tlau88](https://github.com/tlau88), [strugee](https://github.com/strugee), [oparoz](https://github.com/oparoz), [mathiasbynens](https://github.com/mathiasbynens), [edg2s](https://github.com/edg2s), [dnkolegov](https://github.com/dnkolegov), [dhardtke](https://github.com/dhardtke), [wirehead](https://github.com/wirehead), [thorn0](https://github.com/thorn0), [styu](https://github.com/styu), [mozfreddyb ❤️](https://github.com/mozfreddyb), [mikesamuel](https://github.com/mikesamuel), [jorangreef](https://github.com/jorangreef), [jimmyhchan](https://github.com/jimmyhchan), [jameydeorio](https://github.com/jameydeorio), [jameskraus](https://github.com/jameskraus), [hyderali](https://github.com/hyderali), [hansottowirtz](https://github.com/hansottowirtz), [hackvertor](https://github.com/hackvertor), [freddyb](https://github.com/freddyb), [flavorjones](https://github.com/flavorjones), [djfarrelly](https://github.com/djfarrelly), [devd](https://github.com/devd), [camerondunford](https://github.com/camerondunford), [buu700](https://github.com/buu700), [buildog](https://github.com/buildog), [alabiaga](https://github.com/alabiaga), [Vector919](https://github.com/Vector919), [Robbert](https://github.com/Robbert), [GreLI](https://github.com/GreLI), [FuzzySockets](https://github.com/FuzzySockets), [ArtemBernatskyy](https://github.com/ArtemBernatskyy), [@garethheyes](https://twitter.com/garethheyes), [@shafigullin](https://twitter.com/shafigullin), [@mmrupp](https://twitter.com/mmrupp), [@irsdl](https://twitter.com/irsdl),[ShikariSenpai](https://github.com/ShikariSenpai), [ansjdnakjdnajkd](https://github.com/ansjdnakjdnajkd), [@asutherland](https://twitter.com/asutherland), [@mathias](https://twitter.com/mathias), [@cgvwzq](https://twitter.com/cgvwzq), [@robbertatwork](https://twitter.com/robbertatwork), [@giutro](https://twitter.com/giutro), [@CmdEngineer\_](https://twitter.com/CmdEngineer_), [@avr4mit](https://twitter.com/avr4mit), [davecardwell](https://github.com/davecardwell), [Develop-KIM](https://github.com/Develop-KIM), [asamuzaK](https://github.com/asamuzaK), [fishjojo1 ❤️](https://github.com/fishjojo1), [Rikuxx0](https://github.com/Rikuxx0), [donmccurdy](https://github.com/donmccurdy), [hhk-png](https://github.com/hhk-png), [elrion018](https://github.com/elrion018), [michalnieruchalski-tiugo](https://github.com/michalnieruchalski-tiugo), [reey](https://github.com/reey), [KanhaKanhaiya](https://github.com/KanhaKanhaiya), [odaysec](https://github.com/odaysec), [Akokonunes](https://github.com/Akokonunes), [alirezarouhbakhsh](https://github.com/alirezarouhbakhsh), [Jaybhade](https://github.com/Jaybhade) и особенно [@securitymb ❤️](https://twitter.com/securitymb) и [@masatokinugawa ❤️](https://twitter.com/masatokinugawa)