Безопасность и жизненный цикл
Rector очищает поддерживаемые границы содержимого, но сам редактор не является границей доверия. Считайте документы, данные буфера обмена, результаты обратных вызовов, файлы, URL и конфигурацию плагинов недоверенными, если они поступили извне текущего процесса приложения.
Модель содержимого
Встроенные текстовые плагины хранят ограниченное подмножество HTML для внутристрочного форматирования. Общая подсистема очистки удаляет неподдерживаемые теги, атрибуты, схемы URL и свойства стилей. Встроенные рендереры повторяют очистку, потому что сохранённое содержимое могло миновать редактор.
Способ очистки зависит от контекста:
- для обычного текста используйте
textContent; sanitizeHtml()применяйте только к поддерживаемому подмножеству форматирования;- для ссылок и мультимедиа используйте утилиты URL Rector или явные разрешающие списки протоколов;
- для управляемых пользователем стилей разрешайте только перечисленные свойства CSS;
- не передавайте произвольный HTML в
innerHTML,insertAdjacentHTMLи места вставки разметки SVG.
Произвольный HTML
Блок Raw предназначен для намеренно принимаемого HTML, но и он применяет политику библиотеки для такого содержимого. Не используйте его для вывода данных администратора, встроенных сценариев, произвольных iframe, атрибутов обработчиков или шаблонов приложения.
Если приложению нужна более богатая вставка, создайте типизированный плагин. Пусть его данные хранят разрешённую службу и идентификатор ресурса, а рендерер строит DOM с фиксированными атрибутами.
URL и внешнее содержимое
Разрешайте только схемы, необходимые возможности. Для большинства ссылок это https: и при необходимости http:. Отклоняйте или нормализуйте javascript:, опасные формы data:, учётные данные в URL и специальные схемы приложения, если проверенное требование явно их не разрешает.
Внешний предпросмотр, упоминания, загрузки, опросы и встраиваемые материалы должны получать структурированные данные из обратных вызовов приложения. Rector не выполняет аутентификацию таких служб, не проверяет полномочия, не ограничивает частоту запросов и не проверяет серверные правила предметной области.
Файлы
Клиентские MIME и расширение — подсказки, а не доказательство типа. До загрузки ограничивайте количество и размер средствами приложения. На сервере проверяйте фактическое содержимое, создавайте безопасные имена, изолируйте хранилище и отдавайте файлы с правильными заголовками.
Отзывайте каждый объектный URL после замены предварительного просмотра или уничтожения блока.
Буфер обмена
Маршрутизация вставки сначала сопоставляет объявленные возможности плагинов, а затем передаёт плагину нормализованное событие. Плагин всё равно обязан очистить event.element, проверить event.file и осторожно разобрать event.data.
Асинхронная вставка участвует в одной транзакции истории через waitForPaste(). Отклонение этого промиса не должно оставлять частично зафиксированное состояние документа.
Доверие к расширениям
icon плагина, код расширения, обратные вызовы конфигурации и фабрики рендереров выполняются с правами приложения. Устанавливайте расширения как зависимости кода и проверяйте их соответствующим образом. Очистка HTML не защищает от вредоносного плагина с прямым доступом к DOM, сети или хранилищу.
Поддерживаются только пути импорта из карты exports пакета. Импорт внутренних файлов ослабляет границы обновления и безопасности.
Диагностика и конфиденциальность
Встроенная служебная диагностика не содержит пользовательское содержимое. Она может включать название операции, тип плагина или блока, версии, длительность и класс ошибки. При отправке в телеметрию не добавляйте полный документ, выделенный текст, URL, имена файлов или идентификаторы пользователей.
Жизненный цикл редактора
Редактор владеет ресурсами от успешного создания до destroy():
const editor = createEditor(config)
try {
// Работа с редактором
} finally {
editor.destroy()
}При ошибке сборки Rector откатывает уже полученные ресурсы. Если ошибка возникла во время освобождения, остальные действия очистки продолжаются, а в диагностику может поступить cleanup.failed.
Освобождение ресурсов расширения
destroy(element) блочного плагина вызывается для каждого принадлежащего ему элемента. destroy() внутристрочного плагина вызывается при уничтожении редактора. destroy() внутристрочного инструмента освобождает общие ресурсы инструмента. Временная группа элементов управления может вернуть собственный destroy().
Учитывайте ресурсы явно:
class LiveBlock {
controllers = new WeakMap()
render(data) {
const element = document.createElement('div')
const controller = new AbortController()
this.controllers.set(element, controller)
this.load(data.id, controller.signal)
return element
}
destroy(element) {
this.controllers.get(element)?.abort()
this.controllers.delete(element)
}
}Используйте WeakMap для состояния отдельного элемента, потому что один экземпляр плагина может отображать несколько блоков. Не храните последний созданный элемент в одном общем поле.
Жизненный цикл рендерера
Каждой установке должна соответствовать очистка, а каждому подключению стилей — освобождение:
const renderer = createEditorRenderer()
const styleOwner = renderer.injectStyles()
renderer.renderTo(data, container)
renderer.destroy(container)
styleOwner.destroy()При замене одного представления приложения другим очистка должна выполняться, даже если отображение или последующий код приложения выбросил исключение.
Проверка уничтожения
Для каждого расширения с состоянием проверьте уничтожение:
- до завершения асинхронного запроса;
- при открытом всплывающем элементе или меню;
- после многократных циклов отображения и сохранения;
- после отмены и повтора;
- при нескольких блоках с одним экземпляром плагина;
- при двух редакторах или рендерерах с одинаковыми URL таблиц стилей.
После очистки не должны оставаться глобальные обработчики, наблюдатели, таймеры, кадры анимации, запросы, объектные URL, ссылки на отсоединённый DOM или стили без владельца.