Веб-разработка

Content Security Policy (CSP): практическое руководство для инженеров

Разберитесь, как работает Content Security Policy, как strict CSP использует nonce и hash, как безопасно внедрять политику, диагностировать нарушения и избегать распространённых ошибок CSP.

SeoNest Team2 мин чтения
Открыть содержание статьи

Content Secrezhim-google-ai.mdurity Policy: практическое руководство для инженеров

Content Security Policy (CSP) — это механизм безопасности, применяемый браузером и позволяющий сайту определять, какие ресурсы могут загружаться или выполняться и какие действия, связанные с безопасностью, разрешены. Его главное практическое назначение — снижать ущерб от cross-site scripting (XSS): даже если вредоносный HTML попадёт на страницу, строгая CSP может не позволить внедрённому JavaScript выполниться. CSP Level 3 — актуальная линия спецификации W3C; по состоянию на сентябрь 2026 года она остаётся Working Draft, а текущая версия черновика W3C датирована 13 августа 2026 года. (w3.org)

CSP не следует считать заменой корректному escaping, sanitization, безопасным шаблонам или исправлению XSS-уязвимостей. Это defense in depth — дополнительный уровень защиты, который принудительно применяется браузером. (web.dev)

Что такое CSP?

Content Security Policy обычно передаётся через HTTP-заголовок ответа Content-Security-Policy:

Content-Security-Policy: default-src 'self'

Это сообщает браузеру, что ресурсы, контролируемые default-src, обычно должны загружаться с того же origin, что и сама страница.

Политика может содержать множество директив, каждая из которых управляет отдельной возможностью:

ДирективаЧто контролирует
script-srcВыполнение и загрузку JavaScript
style-srcЗагрузку CSS и inline-стили
img-srcИзображения
connect-srcfetch(), XHR, WebSocket и похожие соединения
font-srcШрифты
frame-srcФреймы, которые может загружать страница
worker-srcWorkers и service workers
object-srcPlugin/object-ресурсы
base-uriРазрешённые URL для <base>
form-actionАдреса, на которые разрешена отправка форм
frame-ancestorsСайты, которым разрешено встраивать страницу

default-src служит fallback-значением для многих fetch-директив. Например, если img-src отсутствует, изображения могут использовать default-src. Но это правило действует не всегда: например, frame-ancestors, form-action и base-uri не наследуют значение default-src. (w3.org)

Почему CSP важна

Представим уязвимую страницу, которая случайно выводит HTML, контролируемый злоумышленником:

<script>
  stealSession();
</script>

Без дополнительной защиты браузер может выполнить этот код.

Строгая CSP меняет сам вопрос с:

«Это валидный JavaScript?»

на:

«Эта страница явно разрешила выполнение этого JavaScript?»

Именно поэтому CSP способна существенно снизить последствия многих XSS-уязвимостей.

Сложность заключается в том, чтобы определить, какие скрипты считаются доверенными.

Старые конфигурации CSP часто опирались на allowlist доменов:

script-src 'self' https://cdn.example.com

Это лучше, чем разрешать любые скрипты, однако крупными allowlist на основе хостов становится сложно управлять и анализировать их безопасность. Кроме того, некоторые разрешённые origin сами могут предоставлять способы выполнения кода, контролируемого злоумышленником. Поэтому современные рекомендации по CSP отдают предпочтение strict CSP на основе nonce или hash для управления выполнением скриптов. (developer.mozilla.org)

Strict CSP

Базовая strict policy на основе nonce может выглядеть так:

Content-Security-Policy:
  script-src 'nonce-RANDOM_VALUE' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

Ключевой элемент здесь — nonce.

Сервер генерирует новое непредсказуемое значение для каждого ответа:

<script nonce="RANDOM_VALUE" src="/app.js"></script>

То же значение указывается в заголовке CSP:

script-src 'nonce-RANDOM_VALUE'

Браузер выполняет скрипт, потому что значения совпадают. Внедрённый скрипт без правильного nonce будет заблокирован.

Чтобы такая схема действительно обеспечивала защиту, nonce должен быть непредсказуемым и генерироваться заново для каждого ответа. web.dev рекомендует использовать криптографически стойкое значение размером желательно не менее 128 бит. (web.dev)

Не следует генерировать nonce один раз при запуске приложения и затем использовать его постоянно. Если nonce не меняется, он перестаёт выполнять роль одноразового токена авторизации.

Nonce и hash

Nonce и hash решают похожие задачи, но подходят для разных архитектур.

Nonce обычно удобнее для динамически генерируемого HTML. Сервер создаёт новое значение при каждом запросе и добавляет его в разрешённые элементы <script>.

Hash хорошо подходит для стабильного или статически сгенерированного HTML. Вместо случайного значения вычисляется криптографический hash разрешённого скрипта:

script-src 'sha256-AbCdEf...'

Браузер вычисляет hash скрипта и выполняет его только в том случае, если значение совпадает с указанным в политике.

CSP поддерживает hash-выражения SHA-256, SHA-384 и SHA-512. Даже небольшое изменение inline-скрипта изменяет его hash, поэтому build-системам, использующим CSP на основе hash, обычно приходится заново генерировать политику при изменении содержимого скрипта. MDN также описывает дополнительные требования для случаев, когда hash используется для разрешения внешних скриптов, включая соответствующий атрибут integrity. (developer.mozilla.org)

Для SSR-приложения nonce часто оказывается более простым инженерным решением. Для статического HTML, где нежелательно изменять документ при каждом ответе, удобнее могут быть hashes.

Как работает strict-dynamic

Современные приложения часто используют доверенный bootstrap-скрипт, который динамически загружает другие скрипты.

Без дополнительной настройки такие вторичные скрипты могут быть заблокированы.

Добавление:

'strict-dynamic'

позволяет распространять доверие, установленное корректным nonce или hash, на скрипты, загружаемые этим доверенным скриптом. (developer.mozilla.org)

Например:

script-src 'nonce-abc123' 'strict-dynamic'

и:

<script nonce="abc123" src="/bootstrap.js"></script>

позволяют выполнить /bootstrap.js, а также могут разрешить скрипты, которые он впоследствии создаёт и загружает.

Здесь есть важное следствие: в браузерах, применяющих семантику CSP Level 3, host-выражения вроде 'self', https: и явные allowlist хостов в этой директиве script-src игнорируются, когда 'strict-dynamic' работает вместе с валидным доверием через nonce или hash. Поэтому сама цепочка доверенных скриптов становится важной границей безопасности. (developer.mozilla.org)

Если доверенный JavaScript динамически формирует URL скриптов на основе данных, контролируемых злоумышленником, CSP не сможет автоматически исправить такую архитектуру.

Практический пример политики

Реальному приложению обычно требуется больше, чем один script-src.

Например:

Content-Security-Policy:
  default-src 'self';
  script-src 'nonce-{NONCE}' 'strict-dynamic';
  style-src 'self';
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self' https://api.example.com;
  object-src 'none';
  base-uri 'none';
  form-action 'self';
  frame-ancestors 'none';

Это следует воспринимать как архитектурный пример, а не как политику, которую нужно без изменений копировать на любой сайт.

Например, frame-ancestors 'none' запрещает другим страницам встраивать документ и может помогать защищаться от UI-redressing атак. Но приложению, которое намеренно встраивается на сайты клиентов, могут потребоваться конкретные разрешённые parent origins. Эта директива не наследует значение default-src, поэтому при необходимости её нужно задавать отдельно. (w3.org)

Аналогично, connect-src должна включать API, WebSocket endpoints и другие сетевые адреса, которые действительно использует приложение.

Цель состоит не в том, чтобы создать максимально длинную CSP, а в том, чтобы как можно точнее и уже описать допустимое поведение приложения.

Избегайте unsafe-inline

Типичная реакция на ошибки CSP выглядит так:

script-src 'self' 'unsafe-inline'

Это часто сводит на нет одну из самых ценных защит CSP, поскольку произвольный inline JavaScript может получить возможность выполняться.

Лучше переработать такие конструкции:

<button onclick="save()">Save</button>

и перейти к регистрации событий через JavaScript:

<button id="save">Save</button>

<script nonce="{NONCE}">
document
  .getElementById('save')
  .addEventListener('click', save);
</script>

Strict CSP также обычно запрещает JavaScript URL вроде href="javascript:..." и блокирует выполнение строк как кода через механизмы вроде eval(), если явно не разрешён 'unsafe-eval'. (web.dev)

Поэтому добавление 'unsafe-inline' или 'unsafe-eval' только ради устранения ошибок CSP должно быть поводом для расследования, а не стандартным решением.

Внедряйте через Report-Only

Прямое включение строгой политики в production может сломать аналитику, платёжные виджеты, authentication flows, инструменты поддержки клиентов, API-запросы или код приложения, который изначально не проектировался с учётом CSP.

Используйте:

Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'nonce-{NONCE}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

Режим Report-Only проверяет политику, но не применяет блокировку. Сообщения в консоли браузера и CSP reports показывают, что предложенная политика заблокировала бы при реальном применении. (developer.mozilla.org)

Практический процесс внедрения выглядит так:

Наблюдать → классифицировать нарушения → исправлять легитимные зависимости → ужесточать политику → включать enforcement → продолжать мониторинг.

Не добавляйте автоматически каждый адрес из отчёта в allowlist. Нарушение может указывать на устаревший код, неожиданную стороннюю зависимость, расширение браузера, попытку внедрения или реальную необходимость в настройке.

CSP Reporting

Современный механизм отчётности CSP использует Reporting API вместе с заголовком Reporting-Endpoints и директивой CSP report-to:

Reporting-Endpoints:
  csp="https://example.com/csp-reports"

Content-Security-Policy:
  default-src 'self';
  report-to csp;

report-uri считается устаревшим в CSP Level 3 в пользу report-to. При этом актуальная документация MDN по-прежнему показывает использование обоих вариантов там, где требуется совместимость с браузерами без полноценной поддержки report-to. (w3.org)

Сами отчёты тоже следует считать недоверенным вводом. Инфраструктура логирования должна проверять их, ограничивать частоту запросов и безопасно хранить данные, а не предполагать, что каждый report достоверен.

Header или <meta>

CSP также можно передавать через:

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

но для production обычно предпочтительнее HTTP-заголовок.

У CSP, заданной через meta, есть важные ограничения. Она не может использоваться для report-only policies, а CSP Level 3 указывает, что директивы, включая frame-ancestors, report-uri и sandbox, не поддерживаются через meta-механизм. Кроме того, meta policy не может задним числом контролировать ресурсы, которые браузер обработал до того, как встретил её в документе. (w3.org)

Распространённые ошибки CSP

Наиболее частые инженерные ошибки связаны не с синтаксисом, а с неправильной моделью доверия: огромный список хостов воспринимается как сильная защита, nonce используется повторно, домены автоматически добавляются после каждого нарушения, 'unsafe-inline' включается только для исчезновения ошибок, либо предполагается, что default-src 'none' автоматически настраивает такие директивы, как frame-ancestors и form-action.

Ещё одна неочевидная ошибка — отправлять несколько CSP policies и ожидать, что вторая ослабит первую. Несколько принудительно применяемых политик работают одновременно: запрос должен удовлетворять всем применимым политикам. Второй заголовок не может просто переопределить более строгий первый. (w3.org)

Рекомендация SeoNest

Начинайте с реального графа ресурсов приложения, а не с копирования результата универсального CSP generator.

Для динамически рендерящихся приложений предпочтителен корректно сгенерированный nonce для каждого ответа и строгий script-src. Для статических приложений стоит рассмотреть policy на основе hash. Такие чувствительные к безопасности директивы, как object-src, base-uri, form-action и frame-ancestors, следует задавать явно в соответствии с реальными требованиями продукта.

Сначала разверните предлагаемую политику в режиме report-only, изучите нарушения, устраните несовместимые JavaScript-паттерны и только затем включайте enforcement.

Главное — правильно понимать место CSP в модели безопасности: это механизм сдерживания, который принудительно применяется браузером, а не разрешение оставлять XSS-уязвимости неисправленными.

FAQ

Предотвращает ли CSP все XSS-атаки?

Нет. CSP может существенно ограничивать выполнение скриптов и снижать последствия многих XSS-уязвимостей, однако обходы всё ещё возможны, если сами доверенные скрипты предоставляют эксплуатируемое поведение. Корректная обработка входных данных, context-aware output encoding, sanitization и безопасная архитектура приложения по-прежнему необходимы. (web.dev)

Нужно ли каждому сайту использовать default-src 'none'?

Не обязательно. Это строгая начальная позиция, поскольку после неё ресурсы нужно разрешать явно, но итоговые директивы всё равно должны соответствовать архитектуре приложения. Некоторые директивы не наследуют значение default-src.

Что использовать: nonce или hash?

Используйте nonce, если сервер может генерировать и изменять HTML для каждого ответа. Hash часто удобнее для стабильного статического HTML. (developer.mozilla.org)

Можно ли настроить CSP в Nginx?

Да. Nginx может отправлять CSP-заголовки, но CSP на основе nonce обычно требует координации с приложением или слоем rendering, поскольку свежий nonce должен одновременно присутствовать и в заголовке ответа, и в разрешённом HTML.

Как проверить, что CSP работает?

Проверьте фактические response headers, протестируйте ожидаемые сценарии приложения, следите за CSP violations в DevTools браузера, собирайте reports в режиме report-only, намеренно проверяйте запрещённые ресурсы и повторяйте эти тесты после включения enforcement.

Итог

Полезная CSP — это не просто список доверенных доменов. Это явно заданная модель доверия, которую браузер принудительно применяет.

Для многих современных приложений наиболее сильный практический подход — strict policy на основе криптографических nonce или hash, дополненная директивами, ограничивающими framing, отправку форм, plugins и другие ресурсы. Внедряйте её постепенно, измеряйте нарушения до включения enforcement и рассматривайте каждое исключение как архитектурное решение, а не как очередной hostname, который нужно добавить в список.

Источники

  1. W3C — Content Security Policy Level 3, Working Draft, 13 августа 2026 года. (w3.org) W3C CSP Level 3 specification
  2. MDN Web Docs — Content Security Policy (CSP), дата обращения: 19 сентября 2026 года. (developer.mozilla.org) MDN CSP Guide
  3. MDN Web Docs — Content-Security-Policy header, дата обращения: 19 сентября 2026 года. (developer.mozilla.org) MDN CSP Header Reference
  4. web.dev — Mitigate cross-site scripting (XSS) with a strict Content Security Policy (CSP), дата обращения: 19 сентября 2026 года. (web.dev) web.dev Strict CSP Guide
  5. MDN Web Docs — Content Security Policy implementation, дата обращения: 19 сентября 2026 года. (developer.mozilla.org) MDN CSP Implementation Guide
  6. MDN Web Docs — frame-ancestors directive, дата обращения: 19 сентября 2026 года. (developer.mozilla.org) MDN frame-ancestors Reference
  7. MDN Web Docs — Content-Security-Policy-Report-Only, дата обращения: 19 сентября 2026 года. (developer.mozilla.org) MDN CSP Report-Only Reference

SEONEST

Нужна более сильная техническая основа?

Мы создаём production-ready сайты, где SEO, скорость и чистая разработка заложены в архитектуру с самого начала.

Обсудить проект