Санитизация HTML для OneScript по принципу allowlist: в выводе остаются только явно разрешённые теги, атрибуты и протоколы URL — всё остальное удаляется или экранируется. Основное назначение — безопасный показ HTML, отрендеренного из Markdown недоверенных пользователей (например, README пакетов на хабе).
С версии 0.2.0 библиотека закрывает и обратную задачу — безопасную сборку
собственного HTML из недоверенных значений: экранирование значений, валидация имён
атрибутов, сборка атрибутов и ссылок с фильтрацией опасных схем URL
(javascript:/data:/vbscript: и их обфускации).
Публичный интерфейс — один модуль-фасад СанитизацияHTML. Токенизатор,
санитайзер, настройки и таблица сущностей лежат в src/internal и меняются без
оглядки на обратную совместимость.
Безопасный = СанитизацияHTML.Очистить(НедоверенныйHTML); // разбор чужого HTML
Настройки = СанитизацияHTML.ПустыеНастройки(); // свой allowlist
Фрагмент = СанитизацияHTML.Ссылка(Адрес, Текст); // сборка своего HTMLЗависимостей времени выполнения нет — только стандартная библиотека OneScript.
opm install html-sanitizer#Использовать html-sanitizer
БезопасныйHTML = СанитизацияHTML.Очистить(НедоверенныйHTML);Профиль по умолчанию (НастройкиMarkdown) рассчитан на вывод типичного
markdown-рендера, в том числе oscript-md.
Настройки = СанитизацияHTML.ПустыеНастройки() // ни один тег не разрешён
.РазрешитьТег("p")
.РазрешитьТег("a", "href, title")
.РазрешитьПротокол("ftp")
.УстановитьRelВнешнихСсылок("nofollow noopener noreferrer");
БезопасныйHTML = СанитизацияHTML.Очистить(HTML, Настройки);Профиль по умолчанию тоже можно дорабатывать:
Настройки = СанитизацияHTML.НастройкиMarkdown()
.ЗапретитьТег("img") // тег удаляется, содержимое остаётся
.УдалятьТегССодержимым("blockquote") // тег удаляется вместе с содержимым
.УстановитьRelВнешнихСсылок(""); // не добавлять rel внешним ссылкам| Метод | Назначение |
|---|---|
СанитизацияHTML.Очистить(HTML, Настройки = Неопределено) |
санитизация; без настроек — профиль markdown |
СанитизацияHTML.НастройкиMarkdown() |
профиль по умолчанию (таблица ниже) |
СанитизацияHTML.ПустыеНастройки() |
пустой allowlist для своего профиля |
СанитизацияHTML.ЭкранироватьТекст(Текст) |
экранирование текста без разрешённых тегов |
СанитизацияHTML.ЭкранироватьЗначение(Значение) |
то же для произвольного значения: Неопределено — "", не-строки приводятся |
СанитизацияHTML.ИмяАтрибутаДопустимо(Имя) |
валидация имени атрибута: [A-Za-z][A-Za-z0-9-]*, до 64 символов (имя не спасти экранированием — пробел и = проходят сквозь него) |
СанитизацияHTML.Атрибут(Имя, Значение) |
фрагмент имя="значение" с экранированием значения; пустое значение или плохое имя — "" |
СанитизацияHTML.БулевАтрибут(Имя, Условие) |
имя при Условие = Истина и допустимом имени, иначе "" |
СанитизацияHTML.URLБезопасенДляHref(Адрес) |
allowlist схем http/https/mailto + относительные/якорные; обфускации (java	script:, JavaScript:, пробелы/управляющие) нейтрализуются нормализацией |
СанитизацияHTML.Ссылка(Адрес, Текст, Класс = "", Внутренности = Неопределено) |
безопасная <a href>: адрес и текст экранируются, опасная схема — только текст (в <span> с классом) |
Объект настроек, полученный от НастройкиMarkdown() или ПустыеНастройки():
| Метод | Назначение |
|---|---|
.РазрешитьТег(Имя, Атрибуты = "") |
разрешить тег (атрибуты — строка через запятую или массив) |
.ЗапретитьТег(Имя) |
убрать тег из allowlist (содержимое сохраняется) |
.УдалятьТегССодержимым(Имя) |
удалять тег вместе с содержимым (как script) |
.РазрешитьПротокол(Схема) / .ЗапретитьПротокол(Схема) |
протоколы URL для href/src |
.УстановитьRelВнешнихСсылок(Значение) |
rel для внешних ссылок; "" — отключить |
Все методы настроек возвращают сам объект — поддерживаются цепочки вызовов.
| Теги | Разрешённые атрибуты |
|---|---|
p, br, hr |
— |
h1 … h6 |
— |
ul, ol, li |
— |
strong, em |
— |
code, pre |
class — только вида language-* |
blockquote |
— |
a |
href, title (+ автоматический rel для внешних ссылок) |
img |
src, alt, title |
table, thead, tbody, tr |
— |
th, td |
align — только left / center / right |
details, summary |
— |
Протоколы URL: http, https, mailto; относительные и протокол-относительные
(//host/...) ссылки разрешены. Внешним ссылкам (http://, https://, //)
добавляется rel="nofollow noopener" (настраивается).
Теги, удаляемые вместе с содержимым (по умолчанию): script, style,
iframe, frame, frameset, object, embed, applet, form, button,
select, option, textarea, svg, math, template, xmp, noembed,
noframes, noscript, title, head, marquee. Прочие неразрешённые теги
(div, span, …) удаляются с сохранением текстового содержимого.
- В выводе — только разрешённые теги и разрешённые для них атрибуты; весь текст и значения атрибутов экранированы.
- URL в
href/srcпроходят проверку протокола после однократного раскодирования HTML-сущностей (javascript:,:,	и т.п.) и удаления управляющих/невидимых символов (таб, перевод строки,, zero-width, BOM, RTLO/LRO);javascript:,vbscript:,data:и любые схемы вне allowlist приводят к удалению атрибута, включая обфускации смешанным регистром. - Комментарии (включая conditional comments), CDATA, доктайпы, инструкции обработки удаляются целиком; нулевые байты вырезаются до разбора.
- Малформед-HTML не ломает вывод: незакрытые разрешённые теги закрываются,
перепутанный порядок закрытия исправляется, лишние закрывающие теги
отбрасываются, незакрытая кавычка атрибута не «открывает» новые теги,
<вне тега экранируется в<. - Дублирующиеся атрибуты: действует первое вхождение (как в браузере).
- Раскодирование сущностей выполняется ровно один раз — двойное кодирование
(
&#106;…) не даёт исполнимую схему. - Fail-closed: незакрытый опасный тег (
<script>без закрытия) удаляет всё до конца документа; сомнительная схема URL удаляет атрибут.
- Гомоглифы в доменах (
https://еxample.comс кириллической «е») — это валидные URL, они проходят. Защита от фишинга доменов — задача уровня отображения/линтера ссылок, не санитайзера. - RTLO и bidi-символы в обычном тексте сохраняются (в URL — удаляются): влияние на порядок отображения текста не является XSS.
data:-изображения удаляются вместе сsrc— инлайн-картинки из markdown потребуют отдельного решения, сознательный компромисс.- Санитайзер не валидирует семантическую вложенность HTML (например,
liвнеul) — только безопасность и балансировку тегов. - Неизвестные именованные сущности остаются буквальным текстом (
&foo;), числовые сущности за пределами BMP (>) не раскодируются и остаются экранированным текстом.
Наружу выставлен только фасад; всё, что ниже, — деталь реализации:
src/Модули/СанитизацияHTML.os публичный фасад (#Использовать "../internal")
src/internal/Классы/ ТокенизаторHTML, СанитайзерHTML, НастройкиСанитизации
src/internal/Модули/ СущностиHTML
ТокенизаторHTML— однопроходный разбор HTML на токены (текст, открывающий и закрывающий теги) с повторением ключевых решений HTML5-парсера для малформед-входа; комментарии/CDATA/декларации пропускаются на этом уровне.СанитайзерHTML— фильтрация потока токенов по настройкам, проверка URL, балансировка тегов стеком, сериализация безопасного вывода.НастройкиСанитизации— allowlist тегов/атрибутов, протоколы, политикаrel.СущностиHTML— однократное раскодирование и экранирование сущностей.
oneunit execute -d testsКорпус: 93 теста, включая 70+ XSS-векторов по мотивам OWASP XSS Filter Evasion
Cheat Sheet, малформед-корпус, юникод и негативные сценарии настроек. Тестам
внутренностей доступен #Использовать "../src/internal".
MIT