Зачем вообще нужен CSP и почему его боятся
CSP (Content-Security-Policy) — это политика, которую браузер применяет к странице: откуда можно грузить скрипты, стили, картинки и шрифты, куда можно отправлять данные, какие встраивания разрешены. По сути это «контракт» между вашим фронтендом и браузером: попытки загрузить или выполнить что-то вне правил будут заблокированы (в боевом режиме) и или зарепорчены.
Практическая польза CSP для админов и вебмастеров:
- сильно снижает эффект XSS: даже если инъекция случилась, вредоносный JS часто не выполнится;
- ограничивает цепочки атак через сторонние скрипты и виджеты;
- дисциплинирует фронтенд: меньше «магии» в inline-скриптах и случайных CDN;
- помогает закрыть mixed content через директивы апгрейда и блокировки.
Почему CSP внедрять трудно: исторические inline-скрипты в шаблонах, обработчики событий, стили в атрибутах, динамическая подгрузка, A B-тесты, пиксели, «кусочки» от маркетинга. CSP заставляет всё это либо явно описать, либо перепаковать.
Два режима CSP: enforcement и report-only
У CSP есть два заголовка:
Content-Security-Policy— «боевой» режим: браузер блокирует нарушения.Content-Security-Policy-Report-Only— режим наблюдения: браузер ничего не блокирует, но фиксирует нарушения и может отправлять отчёты.
Почти всегда правильный путь для продакшена такой: сначала включаем Report-Only, собираем нарушения, правим фронтенд и политику, а затем переводим в enforcement.
Самая частая ошибка — включить строгий CSP сразу в enforcement и получить «сайт без JavaScript» в пятницу вечером. Report-Only позволяет пройти тот же путь без простоя.
Если вы планируете включать CSP на нескольких сайтах и окружениях (staging prod) и хотите аккуратно разделять конфиги и заголовки, удобнее делать это на уровне веб-сервера или шаблонов приложения, а не руками на каждой странице.
Какие отчёты бывают
В CSP встречаются два механизма репортинга:
report-uri— старый механизм (всё ещё встречается в примерах и легаси-конфигурациях).report-to— современный механизм через Reporting API.
Если вы пока не готовы поднимать приёмник отчётов, всё равно начинайте с Report-Only и смотрите нарушения в DevTools (Console Security). Для больших проектов централизованные отчёты полезнее: они показывают редкие страницы и сценарии, которые не всегда воспроизводятся руками.

Базовые директивы CSP, которые реально используются
Политика CSP состоит из директив. Для типового сайта чаще всего нужны:
default-src— базовый источник «по умолчанию»;script-src— скрипты (центр XSS-защиты);style-src— стили;img-src,font-src,connect-src— картинки, шрифты, XHR fetch WebSocket;frame-srcиframe-ancestors— встраивание фреймов и защита от clickjacking;base-uri— ограничение для HTML base URL;object-src— почти всегда'none';upgrade-insecure-requestsиblock-all-mixed-content— контроль mixed content.
Хорошая стартовая точка — собрать «скелет» политики. Он не идеален, но предсказуем и понятен:
Content-Security-Policy-Report-Only: default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'
Дальше вы расширяете только то, что реально нужно (домены API, виджеты, платёжные фреймы), вместо того чтобы сразу «разрешить всё».
Если хотите параллельно привести в порядок другие заголовки безопасности и не забыть важные нюансы типа always, держите под рукой чек-лист: Security headers для Nginx и Apache: практическая настройка.
Почему script-src — сердце CSP (и где ломается фронтенд)
Исторически сайты часто живут так:
- inline-скрипты в шаблоне:
<script>...</script>; - inline-обработчики:
onclick,onloadи т.п.; - динамическая генерация кода через
evalилиnew Function(часто в старых библиотеках или dev-сборках).
При включении CSP это упирается в ключевые «ослабляющие» токены:
'unsafe-inline'— разрешает inline-скрипты (сильно ухудшает XSS-устойчивость);'unsafe-eval'— разрешает eval-подобные конструкции (тоже ухудшает, часто «тащит» риск из dev-инструментов в прод).
Цель «здорового» CSP — не использовать 'unsafe-inline' и по возможности избегать 'unsafe-eval'.
Nonce и hash: два нормальных способа разрешить нужный inline
Чтобы жить без 'unsafe-inline', браузеру нужно понять, какой именно inline-скрипт «ваш». Для этого есть два механизма: nonce и hash.
Nonce (одноразовый токен на ответ)
Nonce — случайная строка, которая генерируется на каждый HTTP-ответ. Вы добавляете её и в CSP, и в атрибут nonce у конкретного <script> (и при необходимости <style>). Тогда браузер выполнит только тот inline, у которого nonce совпал.
Как это выглядит концептуально (показываю как текст, не «живым» HTML):
Content-Security-Policy: script-src 'self' 'nonce-R4nd0mBase64Token'; object-src 'none'; base-uri 'self'
<script nonce="R4nd0mBase64Token">
window.appConfig = { feature: true };
</script>
Практические нюансы nonce:
- Nonce должен быть криптографически случайным и непредсказуемым.
- HTML-страницы с nonce плохо дружат с «вечным» кешированием на CDN: либо не кешируйте HTML как статик, либо используйте генерацию на edge бэкенде под запрос.
- Nonce проще всего внедрять в SSR (PHP Node Python) и в шаблонизаторы: один nonce на ответ, дальше подставляете в нужные места.
Hash (хэш содержимого inline-скрипта)
Hash — это когда вы считаете хэш (обычно SHA-256) от точного содержимого inline-скрипта и добавляете его в CSP. Браузер разрешит выполнение только если содержимое совпало байт-в-байт.
Концептуально:
Content-Security-Policy: script-src 'self' 'sha256-Base64OfHashHere'; object-src 'none'; base-uri 'self'
Нюансы hash:
- Очень чувствителен к пробелам и переносам: поменяли форматирование в шаблоне — хэш другой.
- Идеален для «стабильных» inline-фрагментов (короткий bootstrap без динамики).
- Плохо подходит для inline с динамическими значениями (CSRF-токены, персонализация и т.п.).
Что выбрать: nonce или hash
- Если у вас SSR и inline-скрипты динамические — берите nonce.
- Если inline-скрипт статичный и вы хотите «прибить» его намертво — берите hash.
- В больших проектах часто смешивают: nonce для динамики, hash для неизменяемых кусочков.
Mixed content: как закрыть проблему на уровне CSP
Mixed content — это когда страница открыта по HTTPS, но тянет ресурсы по HTTP. Браузеры многое уже блокируют, но «плавающие» случаи встречаются: старые ссылки на картинки, легаси API, редиректы с HTTP.
В CSP есть два рычага:
upgrade-insecure-requests— браузер попытается заменить HTTP на HTTPS при запросе ресурса;block-all-mixed-content— жёстко блокировать любой mixed content.
Обычно начинают с upgrade-insecure-requests (сначала в Report-Only, чтобы увидеть масштаб проблемы), затем чистят остатки и включают block-all-mixed-content там, где готовы. Если на проекте ещё нет нормального HTTPS везде, начните с базового: корректный сертификат и единая HTTPS-точка входа. В Fastfox можно быстро подобрать SSL-сертификаты под нужный сценарий (обычно хватает DV, а для организаций — OV EV по требованиям).
Как внедрять CSP без боли: пошаговый план
Шаг 1. Инвентаризация источников фронтенда
Соберите список источников, которые реально участвуют в загрузке и выполнении:
- домены JS CSS (CDN, third-party);
- домены аналитики и пикселей;
- API-домены для
fetch(директиваconnect-src); - домены шрифтов и картинок;
- встраивания (карты, видео, чаты) —
frame-srcиframe-ancestors.
Шаг 2. Включаем Content-Security-Policy-Report-Only
Добавьте заголовок Report-Only на уровне веб-сервера или приложения. Сначала без фанатизма: цель — собрать нарушения, а не «угадать» идеальную политику.
Шаг 3. Разбираем нарушения и чистим фронтенд
Типовые категории репортов:
- inline-скрипты: внедряем nonce hash или выносим код в отдельный файл;
- inline-стили: по возможности вынос в CSS, иначе nonce hash для
style-src; - запросы на «левые» домены: либо легализуем (добавляем домен), либо убираем зависимость;
- eval: меняем библиотеку сборку (часто это dev-сборка, сорсмапы, старый бандлер).
Шаг 4. Переводим в enforcement
Когда отчёты в Report-Only стабилизировались и вы понимаете, что разрешено осознанно, переносите политику в Content-Security-Policy. На первых порах полезно оставить параллельно более строгую политику в Report-Only, чтобы видеть потенциал дальнейшего ужесточения.

Примеры конфигурации: Nginx и Apache
Nginx: Report-Only и enforcement
В Nginx заголовки добавляют через add_header. Важно: для ошибок и редиректов нужен флаг always, иначе заголовок не вернётся на 4xx 5xx и части 3xx.
add_header Content-Security-Policy-Report-Only "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; upgrade-insecure-requests" always;
Когда готовы включать блокировку:
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; upgrade-insecure-requests" always;
Nonce обычно генерируется приложением: Nginx «из коробки» nonce не выпустит без дополнительной логики. Поэтому CSP с nonce чаще формирует backend (или вы прокидываете готовое значение в переменную, если у вас есть соответствующая инфраструктура).
Apache: через Header set
Header always set Content-Security-Policy-Report-Only "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; upgrade-insecure-requests"
Дальше аналогично добавляется Content-Security-Policy для enforcement.
Серые зоны CSP: что часто приходится разрешать осознанно
'unsafe-inline' как временная подпорка
Иногда бизнес требует «включить прямо сейчас», а фронтенд ещё не готов. Тогда компромисс — временно добавить 'unsafe-inline' в script-src в режиме Report-Only и параллельно запланировать миграцию на nonce hash. В enforcement это лучше не тянуть.
'unsafe-eval' и dev prod сборки
Если CSP ругается на eval, это часто признак того, что в продакшене:
- оказалась dev-сборка библиотеки;
- бандлер трансформер использует eval для сорсмапов;
- на странице живёт старый виджет.
Задача админа DevOps — помочь быстро локализовать источник и убрать причину, а не «навсегда разрешить eval». Обычно в отчётах консоли видно, что именно пыталось выполниться, и с какой страницы это пришло.
Сторонние виджеты и цепочки доменов
Маркетинговые и чат-виджеты любят подтягивать дополнительные скрипты с новых доменов. Это ломает строгую политику: сегодня домен один, завтра — два. Здесь помогает дисциплина:
- фиксировать список разрешённых доменов и вводить изменения через процесс (пусть даже простой);
- по возможности проксировать критичные ассеты через свой домен;
- держать отдельную «песочницу» для лендингов, где политика мягче (и риск контролируемый).
Если вы выносите приложения лендинги в отдельные окружения и вам нужны предсказуемые политики заголовков на уровне веб-сервера, часто удобнее использовать отдельный VDS: там проще унифицировать Nginx Apache-конфиги, логи и rollout CSP между сервисами.
Минимальный строгий профиль CSP для типового сайта
Ниже — ориентир, не универсальный рецепт. Он предполагает, что вы уже убрали inline или перевели его на nonce hash, и что вам не нужны плагины объекты:
Content-Security-Policy: default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; upgrade-insecure-requests
Дальше расширяйте только то, что действительно необходимо: отдельные домены для API в connect-src, CDN изображений в img-src, платёжный провайдер в frame-src и т.п.
Отладка CSP: короткий чек-лист для продакшена
- Начинайте с Report-Only и собирайте нарушения минимум несколько дней (учтите редкие страницы и сценарии).
- Разделяйте окружения: staging может быть мягче, но отчёты должны идти и там.
- Следите за
connect-src: именно он чаще всего ломает API-запросы, WebSocket, отправку логов метрик с фронта. - Не забывайте про
frame-ancestors: если сайт встраивают партнёры, вариант'self'может оказаться несовместим. - Mixed content: добавьте
upgrade-insecure-requests, затем закройте остатки и включайтеblock-all-mixed-contentпо необходимости. - Проверяйте заголовок на всех ответах (включая 301 302 4xx 5xx) — для этого и нужен
always.
Как CSP сочетается с другими security headers
CSP — не «серебряная пуля», а часть набора security headers. Лучше всего он работает рядом с корректным HTTPS, HSTS и аккуратной конфигурацией веб-сервера. Внедряйте поэтапно: CSP сложнее остальных заголовков, потому что напрямую завязан на поведение фронтенда.
Если хотите системно пройтись по TLS-настройкам (чтобы не оставалось «дыр» рядом с CSP), полезно свериться с практиками: Практики настройки SSL TLS в 2025.
Хороший признак зрелости: у вас есть базовая политика CSP, изменения сначала идут через Report-Only, а фронтенд-команда понимает, что такое nonce hash и зачем они нужны.
Итог: практичная стратегия внедрения CSP
Рабочая стратегия такая: включите Content-Security-Policy-Report-Only, разберите реальные нарушения, уберите inline-скрипты через nonce или hash, закройте mixed content директивой upgrade-insecure-requests, и только после этого переводите политику в enforcement. Так вы получаете реальную защиту от XSS и контроль над источниками, не ломая продакшен.
Дальше обычно ужесточают политику итеративно: режут лишние домены, уходят от data: там, где можно, и заводят отдельные политики для разных приложений поддоменов.


