{"index":3,"slug":"editorial-2027-12-practice-author-manifesto","title":"Security headers: CSP и HSTS без иллюзии защиты","excerpt":"Как CSP и HSTS ограничивают браузер, почему nonce и HTTPS не заменяют исправление XSS и как вводить политики с проверяемым откатом.","contentHtml":"

Проблема становится видимой после XSS или downgrade-атаки: приложение отдаёт страницу по HTTPS, но разрешает произвольный inline-скрипт или продолжает открываться по HTTP. Симптомы можно увидеть в DevTools: CSP фиксирует нарушение или отсутствует вовсе, а HTTP-запрос сначала доходит до редиректа. Цена ошибки — выполнение чужого кода в контексте origin, утечка токена и потеря данных. Один security header не закрывает весь риск.

Ошибка начинается с копирования длинной строки заголовка без карты ресурсов и момента включения политики. CSP управляет тем, откуда страница может загружать и выполнять ресурсы. HSTS говорит браузеру обращаться к домену по HTTPS после получения политики через доверенный HTTPS-ответ. Ни один заголовок не исправляет серверный XSS, плохой сертификат, mixed content или секрет, который уже попал в JavaScript.

Тезис: заголовок — это граница, а не доказательство безопасности

Политику нужно проектировать от конкретной страницы. Сначала назовите нужные скрипты, стили, изображения, API и фреймы. Затем разрешите только эти источники и отдельно проверьте отрицательные сценарии. Так CSP становится исполняемым контрактом браузера, а не декоративной строкой в конфигурации.

default-src задаёт запасное правило для типов ресурсов, но не заменяет явные директивы. script-src управляет JavaScript. object-src 'none' закрывает загрузку plugin/object, если она не нужна. base-uri 'self' ограничивает изменение базового URL. Такая политика уменьшает поверхность атаки, но не превращает небезопасный sink в безопасный.

Механизм CSP

Браузер получает Content-Security-Policy вместе с ответом и сопоставляет каждую попытку загрузки или выполнения с директивой. При несовпадении ресурс блокируется в режиме enforce. В режиме Report-Only браузер отправляет нарушение для анализа, но не блокирует действие. Поэтому отчёт помогает собрать карту, но сам по себе не является исправлением.

Nonce нужен для конкретного inline-скрипта, который пока нельзя вынести в файл. Сервер генерирует непредсказуемое значение заново для каждого ответа, вставляет его в CSP и в атрибут нужного script. Статическая строка не даёт защиты: тот, кто её узнает, получает разрешение. Полный nonce не стоит писать в диагностические логи.

Симптом → причина → проверка → действие
СимптомПричинаПроверкаДействие
Inline-скрипт заблокированНет nonce или разрешение слишком узкоеСверить директиву script-src и атрибут nonceВынести код в файл или выдать свежий nonce только этому скрипту
В отчёте много нарушенийПолитика собрана без карты ресурсовСгруппировать отчёты по директиве, URL и типу ресурсаУдалить лишние источники и обосновать каждый оставшийся
Сайт всё ещё доступен по HTTPБраузер ещё не получил HSTS или первый переход был HTTPПроверить заголовок в HTTPS-ответе и цепочку переходовИсправить HTTPS и redirect; отдельно оценить требования preload
Поддомен перестал открыватьсяincludeSubDomains включили до проверки всех имёнПроверить сертификаты, HTTPS и endpoint каждого поддоменаРасширять область и max-age только после контролируемой проверки
После CSP остаётся XSSНебезопасный HTML или sink не исправленПроверить контекстное экранирование и места вроде innerHTMLИсправить источник XSS; CSP оставить дополнительным барьером
\"Граф
Два независимых слоя защиты: CSP управляет ресурсами страницы, HSTS — схемой соединения. Один заголовок не заменяет другой.

Минимальный рабочий пример

Ниже — учебная функция, которая собирает заголовки из nonce и срока HSTS. Она не устанавливает заголовки в серверном ответе и не проверяет шаблонизатор. Проверяемый результат показывает только форму политики: nonce присутствует в CSP, а HSTS содержит числовой срок и includeSubDomains.

import { buildSecurityHeaders } from './security-headers.mjs'; const result = buildSecurityHeaders({ nonce: '7c2f1b8e9a4d6f0c', hstsMaxAge: 31536000 }); if (!result.ok) throw new Error(result.reason); response.setHeader('Content-Security-Policy', result.headers['Content-Security-Policy']); response.setHeader('Strict-Transport-Security', result.headers['Strict-Transport-Security']);

В реальном шаблоне nonce должен попасть только в разрешённый inline-скрипт:

<script nonce=\"7c2f1b8e9a4d6f0c\">startApplication();</script>

Пример не доказывает, что приложение безопасно. Он проверяет сборку значения и показывает границу между генерацией заголовка и его применением. Тесты должны отдельно проверить отсутствие случайного unsafe-inline, соответствие nonce в двух местах и отправку заголовков только по нужному маршруту.

Механизм HSTS

HSTS начинает действовать после того, как браузер получил Strict-Transport-Security через доверенный HTTPS-ответ. Большой max-age не защищает самый первый HTTP-переход, если домен ещё неизвестен браузеру. Он также не исправляет сертификат или mixed content. Для защиты первого перехода существует preload с отдельными требованиями и риском: сначала нужно проверить готовность домена.

includeSubDomains распространяет правило на поддомены. Включайте его только после проверки всех имён, которые ещё нужны пользователям, API и административным инструментам. Если один поддомен не умеет HTTPS, браузер перестанет подключаться к нему по HTTP на весь срок политики. Возможность отката — часть безопасности, а не последняя строка runbook.

Порядок внедрения

  1. Соберите карту ресурсов страницы: scripts, inline-код, eval, стили, изображения, CDN, API, iframe и object. Зафиксируйте нужные источники до написания политики.
  2. Включите CSP в Report-Only. Соберите нарушения по директиве, URL и типу ресурса. Не называйте этот режим блокировкой.
  3. Удалите лишние источники. Inline-код вынесите в файл или замените на nonce. Оставшиеся исключения объясните рядом с конфигурацией.
  4. Переведите CSP в enforce на одной проверяемой странице. Сравните ошибки загрузки с разрешённым списком и проверьте отрицательные сценарии.
  5. Проверьте HTTPS для основного домена и поддоменов. Только после этого добавляйте HSTS, затем осторожно расширяйте max-age и область.
  6. Добавьте версионируемый откат и автоматическую проверку заголовков. Изменение не должно зависеть от ручной правки одного proxy.

Ограничения и критерий готовности

Nonce не санитизирует пользовательский HTML и не исправляет небезопасный sink. Если приложение передаёт строку в innerHTML, разрешённый bootstrap может помочь атакующему выполнить уже загруженный код. Нужны контекстное экранирование и безопасные API. CSP снижает последствия и обнаруживает часть нарушений, но не заменяет исправление причины.

Эта схема не проверяет совместимость целевых браузеров, CDN, service worker, iframe-политику, сертификаты, preload и настройки API-клиента. CSP Level 3 остаётся Working Draft, поэтому директивы и поддержку следует сверять с целевыми браузерами. RFC 6797 описывает HSTS, но не даёт защиты до первого безопасного ответа.

Готовность наступает, когда для конкретной страницы есть карта ресурсов, Report-Only нарушения разобраны, enforce проверен, HSTS подтверждён на каждом нужном домене, а откат воспроизводится. Дополнительный признак — автоматическая проверка заголовков ловит возврат широкого источника или исчезновение обязательной политики. Без этих проверок строка конфигурации остаётся предположением.

Проверяемые источники

"}