Перейти к содержимому
Поставьте ⭐️ проекту перевода GitHub

Безопасность

htmx позволяет определять логику прямо в DOM. Это даёт несколько преимуществ, главное из которых — Локализация поведения (Locality of Behavior), что делает систему проще для понимания и поддержки.

Однако при таком подходе возникают проблемы безопасности: поскольку htmx расширяет возможности HTML, если злоумышленник сможет внедрить произвольный HTML в ваше приложение, он может использовать эти расширенные возможности htmx в плохих целях.

Первое правило веб-разработки на основе HTML всегда звучало так: не доверяйте пользовательскому вводу. Вы должны экранировать весь сторонний, ненадёжный контент, попадающий на ваш сайт. Это помогает предотвратить, среди прочего, XSS-атаки.

Подробная документация по XSS и методам защиты доступна на отличном сайте OWASP, включая Шпаргалку по предотвращению XSS.

Хорошая новость: это давно известная и хорошо изученная проблема, и большинство серверных шаблонизаторов поддерживают автоматическое экранирование контента для предотвращения подобных проблем.

Тем не менее, иногда разработчики сознательно вставляют HTML в обход защит, часто используя функции типа raw() в своих шаблонизаторах. На то могут быть веские причины, но если вставляемый контент поступает из ненадёжного источника, его обязательно нужно очищать, удаляя атрибуты, начинающиеся с hx- и data-hx, а также встроенные теги <script> и другие опасные элементы.

Если вы вставляете сырой HTML и самостоятельно занимаетесь экранированием, рекомендуется использовать белый список разрешённых атрибутов и тегов вместо чёрного списка запрещённых.

Конечно, баги случаются, и разработчики не идеальны, поэтому важно использовать многоуровневый подход к безопасности веб-приложений. htmx предоставляет собственные инструменты для дополнительной защиты.

Рассмотрим их подробнее.

Первый инструмент безопасности в htmx — атрибут hx-disable. Он отключает обработку всех htmx-атрибутов как для элемента, на котором указан, так и для всех вложенных элементов.

Например, если вам необходимо вставить необработанный HTML-код в шаблон (хотя это и не рекомендуется!), вы можете обернуть его в div с атрибутом hx-disable:

<div hx-disable>
<%= raw(user_content) %>
</div>

htmx не будет обрабатывать какие-либо htmx-атрибуты или функции, обнаруженные в таком контенте. Этот атрибут нельзя отменить путём внедрения дополнительного контента: если атрибут hx-disable присутствует где-либо в родительской иерархии элемента, htmx не будет обрабатывать этот элемент.

Ещё один аспект безопасности связан с кэшем истории htmx. Некоторые страницы могут содержать конфиденциальные данные, которые не следует сохранять в кеше localStorage пользователя. Вы можете исключить определённую страницу из кэша истории, добавив на страницу атрибут hx-history со значением false.

htmx также предоставляет параметры конфигурации, связанные с безопасностью:

  • htmx.config.selfRequestsOnly - при значении true разрешает только запросы к домену текущего документа
  • htmx.config.allowScriptTags - определяет, будет ли htmx обрабатывать теги <script> в загружаемом контенте. Для отключения этой функции установите значение false
  • htmx.config.historyCacheSize - можно установить в 0, чтобы полностью отключить сохранение HTML в кэше localStorage
  • htmx.config.allowEval - при значении false отключает все функции htmx, использующие eval:
    • фильтры событий
    • атрибуты hx-on:
    • hx-vals с префиксом js:
    • hx-headers с префиксом js:

Важно: все функции, связанные с eval(), можно заменить собственной JavaScript-реализацией, используя событийную модель htmx.

Если вам нужно разрешить запросы к определённым доменам помимо текущего, но не оставлять систему полностью открытой, вы можете использовать событие htmx:validateUrl. В этом событии:

  1. URL запроса доступен в свойстве detail.url
  2. Также имеется флаг sameHost (совпадение с текущим доменом)

Вы можете проверить эти значения и, если запрос не соответствует вашим критериям, вызвать preventDefault() для отмены запроса.

Пример использования:

document.body.addEventListener('htmx:validateUrl', function (evt) {
// разрешать запросы только к текущему серверу и myserver.com
if (!evt.detail.sameHost && evt.detail.url.hostname !== "myserver.com") {
evt.preventDefault();
}
});

Браузеры также предоставляют инструменты для дополнительной защиты веб-приложений. Наиболее мощный инструмент - Политика безопасности контента (Content Security Policy). С помощью CSP вы можете указать браузеру, например, не выполнять запросы к сторонним хостам, не исполнять встроенные скрипты и т. д.

Пример CSP в теге meta:

<meta http-equiv="Content-Security-Policy" content="default-src 'self';">

Это указывает браузеру: «Разрешать соединения только с исходным (основным) доменом». Данная настройка будет избыточной при использовании htmx.config.selfRequestsOnly, но многоуровневый подход к безопасности является оправданным и, фактически, идеальным решением для защиты приложений.

Полное рассмотрение CSP выходит за рамки данного документа, но статья на MDN предоставляет хорошую отправную точку для изучения этой темы.

Генерация и проверка CSRF-токенов обычно являются задачами backend-части, но htmx может автоматически отправлять CSRF-токен с каждым запросом, используя атрибут hx-headers. Этот атрибут необходимо добавить к элементу, инициирующему запрос, или к одному из его родительских элементов. Это делает элементы html и body эффективными глобальными контейнерами для добавления CSRF-токена в заголовок HTTP-запроса, как показано ниже.

<html lang="en" hx-headers='{"X-CSRF-TOKEN": "CSRF_TOKEN_INSERTED_HERE"}'>
:
</html>
<body hx-headers='{"X-CSRF-TOKEN": "CSRF_TOKEN_INSERTED_HERE"}'>
:
</body>

Эти элементы обычно уникальны в HTML-документе, и их должно быть легко найти в шаблонах.