Броня для фронтенда: как настроить Content Security Policy (CSP) и наглухо закрыть сайт от XSS
Сайт может быть написан на чистом коде, регулярно обновляться и стоять за Cloudflare, но одна-единственная дыра в стороннем плагине, форме комментариев или забытом виджете чата открывает прямую дорогу для XSS-инъекций (Cross-Site Scripting). Злоумышленнику достаточно подсунуть вредоносный <script>, чтобы перехватить клиентские сессии, украсть авторизационные куки, подменить платежные реквизиты или превратить браузеры посетителей в ферму для майнинга.
Большинство разработчиков пытаются спасаться экранированием пользовательского ввода на бэкенде. Это базовая гигиена, но полагаться только на нее наивно. Вторым и самым жестким эшелоном обороны выступает CSP (Content Security Policy) — HTTP-заголовок, который диктует браузеру четкие правила: откуда можно грузить скрипты, стили, шрифты и картинки, а любые неавторизованные попытки исполнения чужого кода рубит на корню.

Как работает механизм CSP на практике
CSP превращает браузер клиента в неподкупного валидатора. Сервер вместе с HTML-страницей отдает в заголовках белый список доверенных источников. Если на странице вдруг оказывается JS-код со стороннего вредоносного домена или инлайн-скрипт, внедренный через уязвимый инпут, браузер просто отказывается его выполнять и выбрасывает ошибку в консоль.
Ключевые директивы, из которых собирается рабочая политика:
- default-src — базовый уровень для всех типов ресурсов, если для них не заданы индивидуальные правила. Обычно выставляется в
'self'(только текущий домен). - script-src — главный рубеж против XSS. Определяет доверенные хосты, с которых разрешено подтягивать и выполнять JavaScript.
- style-src — регулирует загрузку CSS-таблиц и предотвращает атаки через подмену стилей (CSS Injection).
- img-src и font-src — изолируют источники графики и кастомных веб-шрифтов (например, Google Fonts или локальные WOFF2).
- connect-src — жестко ограничивает эндпоинты, на которые фронтенд может отправлять данные через
fetch,XHRилиWebSocket. - frame-ancestors — запрещает встраивать ваш сайт в чужие
<iframe>, полностью нейтрализуя кликджекинг (Clickjacking).
Примеры настройки заголовков: от боевого конфига до веб-сервера
Настройка Content Security Policy всегда начинается с проектирования политики под конкретный стек проекта. Если на сайте подключены внешние счетчики аналитики (Яндекс Метрика, Google Analytics) и CDN-шрифты, базовый заголовок будет выглядеть так:
Content-Security-Policy: default-src 'self'; script-src 'self' [https://mc.yandex.ru](https://mc.yandex.ru) [https://www.googletagmanager.com](https://www.googletagmanager.com); style-src 'self' 'unsafe-inline' [https://fonts.googleapis.com](https://fonts.googleapis.com); font-src 'self' [https://fonts.gstatic.com](https://fonts.gstatic.com); img-src 'self' data: [https://mc.yandex.ru](https://mc.yandex.ru); connect-src 'self' [https://mc.yandex.ru](https://mc.yandex.ru); frame-ancestors 'none';
Внедрить заголовок на уровне сервера можно несколькими путями:
- Конфигурация Nginx. Самый производительный вариант. Добавьте директиву в блок
serverилиlocation:add_header Content-Security-Policy "default-src 'self'; script-src 'self' [https://trusted.cdn.com](https://trusted.cdn.com);" always; - Конфигурация Apache (.htaccess). Если проект работает на Apache, используйте модуль заголовков:
Header always set Content-Security-Policy "default-src 'self'; script-src 'self';" - Через мета-тег в HTML. Запасной вариант, если нет прямого доступа к конфигурации сервера:
<meta http-equiv="Content-Security-Policy" content="...">. Учтите, что через мета-тег нельзя передать некоторые директивы вродеframe-ancestorsиreport-uri.
Пошаговый пайплайн безопасного внедрения без поломки сайта
Главный страх при внедрении жесткой политики — случайно сломать верстку, интерфейс личного кабинета или отрезать маркетинговую аналитику. Чтобы выкатка на боевой сервер прошла гладко, используйте проверенный алгоритм:
- Запуск в режиме мониторинга (Report-Only). Вместо основного заголовка отдавайте
Content-Security-Policy-Report-Onlyс параметромreport-uri /csp-report-endpoint/. В этом режиме браузер ничего не блокирует, но аккуратно отправляет JSON-отчеты о каждом нарушении на ваш сервер или в сервис вроде Sentry. - Анализ логов и сбор белого списка. Покрутите сайт в режиме Report-Only неделю под реальным трафиком. Вы сразу увидите все легитимные сторонние скрипты, трекеры и динамические виджеты, о которых забыли фронтендеры, и внесете их в конфиг.
- Отказ от ‘unsafe-inline’ через Nonce. Инлайн-скрипты внутри HTML — главная брешь. Чтобы не открывать шлюзы директивой
'unsafe-inline', внедряйте механизм одноразовых токенов (Nonce): сервер генерирует случайную строку для каждого запроса, отдает ее в CSP (script-src 'nonce-R4nd0m123') и проверяет наличие этого атрибута в теге<script nonce="R4nd0m123">. - Финальное переключение в боевой режим. Когда в логах мониторинга перестанут сыпаться ложные срабатывания, замените заголовок на строгий
Content-Security-Policy.
Грамотно настроенный CSP не отменяет необходимости следить за чистотой кода и валидировать входящие данные на бэкенде, но он делает цену любой случайной оплошности ничтожной. Даже если злоумышленник найдет способ пропихнуть вредоносную строку через форму обратной связи, браузер пользователя просто проигнорирует команду и молча заблокирует выполнение чужого скрипта.
