Ты наконец выложил свою «Змейку» на публичный адрес, рассказал друзьям, и в комментарии кто-то написал не просто «крутая игра», а добавил кусочек JavaScript. Из-за ошибки в коде страница вывела этот комментарий как часть HTML, и теперь в браузере каждого посетителя выполняется чужой скрипт. Он может украсть данные, подменить кнопку «Старт игры» на фишинговую ссылку или отправить запросы от имени пользователя. Именно от таких ситуаций защищает Content Security Policy — специальный HTTP-заголовок, который явно указывает браузеру, откуда разрешено загружать код, стили, картинки и шрифты.
Ниже — разбор CSP простыми словами: что это, почему это важно, какие директивы существуют, как настроить его в проекте на Next.js и как не сломать при этом свой сайт. Мы не будем углубляться в криптографию и тонкости каждого браузера, но к концу текста ты поймёшь, как составить рабочую политику для своего приложения и почему без неё в современном вебе делать нечего.
Содержание
- Что такое Content Security Policy простыми словами
- Зачем нужен CSP
- Чем CSP отличается от защиты от ботов
- Основные директивы коротко
- Как настроить CSP в проекте курса на Next.js
- Типичная ошибка новичка: включить строгий CSP и сломать сайт
- Как проверить, что CSP настроен и не сломал сайт
- Частые вопросы
- Заключение
- Источники
Что такое Content Security Policy простыми словами
Content Security Policy, или сокращённо CSP, — это механизм безопасности, который веб-сервер передаёт браузеру в виде HTTP-заголовка. Этот заголовок содержит набор правил: откуда браузеру разрешено загружать разные типы ресурсов и какой код можно выполнять на странице. Если ресурс или скрипт не попадает под эти правила, браузер просто не загружает его и выводит предупреждение в консоль разработчика.
Проще всего представить CSP как охрану у входа на вечеринку. Страница — это сама вечеринка, а скрипты, стили, картинки, шрифты и запросы к API — это гости. У охраны есть список: кого пускать, а кого нет. Если гость в списке, его пропускают. Если нет — стоят у двери, сколько бы он ни просил. CSP работает так же: браузер перед загрузкой каждого ресурса сверяет его источник с правилами заголовка.
CSP — это не плагин, не библиотека и не настройка в панели управления хостингом. Это обычный HTTP-заголовок, который отдаётся вместе с HTML-страницей. Его может задать сервер, фреймворк или обратный прокси. Браузер получает страницу, читает заголовок и начинает применять правила сразу же, до того как выполнить какой-либо JavaScript.
Самый короткий пример заголовка выглядит так:
Content-Security-Policy: default-src 'self'
Эта запись означает: «По умолчанию загружай ресурсы только с того же домена, с которого пришла сама страница». Если на странице встретится скрипт с внешнего адреса, браузер не выполнит его. Если картинка будет подгружаться с чужого сайта — браузер её не покажет. Это и есть суть Content Security Policy: явный белый список доверенных источников.
Зачем нужен CSP
Главная задача CSP — защита от XSS-атак, то есть от межсайтового выполнения сценариев. XSS возникает, когда злоумышленнику удаётся внедрить свой JavaScript в страницу, которую видят другие пользователи. Классический пример — форма комментария, где введённый текст выводится на сайт без должной обработки. Если вместо текста вставить тег <script>, браузер посетителя выполнит этот код так, будто он является частью сайта.
Без CSP внедрённый скрипт получает практически неограниченные возможности: читать куки и токены пользователя, подменять содержимое страницы, отправлять запросы к твоему API от имени пользователя, перенаправлять на фишинговые страницы. Если на странице есть админка или личный кабинет, последствия могут быть серьёзными даже для небольшого проекта.
CSP не устраняет саму уязвимость в коде, но сильно ограничивает урон от неё. Даже если злоумышленник сумел вставить <script>, браузер не выполнит его, если источник скрипта не указан в политике. Внедрённый inline-скрипт, который лежит прямо в HTML, тоже будет заблокирован, если политика не разрешает 'unsafe-inline'. Так CSP превращает потенциальную катастрофу в ошибку в консоли и неработающий кусочек страницы.
Кроме защиты от XSS, CSP помогает и в других сценариях:
- Защита от инжектов через стили. Директива
style-srcконтролирует, откуда берутся CSS-файлы. Это мешает злоумышленнику подменить внешний вид страницы через внедрённый стиль. - Предотвращение кликджекинга. Директива
frame-ancestorsзапрещает встраивать твой сайт во фреймы на чужих доменах, что закрывает атаку, при которой пользователя обманом заставляют кликнуть по скрытой кнопке. - Контроль за внешними подключениями.
connect-srcограничивает, куда страница может отправлять запросы из JavaScript. Это полезно, если злоумышленник пытается увести данные на свой сервер. - Снижение риска от скомпрометированных сторонних библиотек. Если ты подключаешь скрипт с CDN, а этот CDN взломан, CSP не даст загрузить вредоносный код, если его источник не указан в политике.
Коротко: CSP — это страховочная сетка. Она не заменяет чистый код, проверку входных данных и правильное хранение секретов, но резко снижает последствия человеческих ошибок и известных уязвимостей. Про хранение токенов и паролей мы писали отдельно в статье «Хранение секретов и токенов».
Чем CSP отличается от защиты от ботов
В курсе мы уже разбирали «Защиту от перебора и ботов». Там речь шла о том, как не дать автоматическим программам долбить твой сервер: подбирать пароли, спамить формы регистрации, сканировать админки. Такая защита работает на уровне сервера: она смотрит на входящие запросы, считает их частоту, проверяет капчу или токены и решает, пускать ли конкретный запрос.
CSP работает совсем на другом уровне. Он защищает не сервер, а уже загруженную в браузере страницу пользователя. Его задача — не отсеять бота, а не дать браузеру выполнить или загрузить что-то не то. Если провести аналогию с домом, защита от ботов — это охрана на подъездной дороге, которая решает, кого впускать на территорию. CSP — это внутренняя система доступа внутри квартиры, которая не даёт постороннему включить чужой чайник или передвинуть мебель, даже если он каким-то образом оказался внутри.
Оба механизма важны, и они дополняют друг друга:
| Что защищает | Защита от ботов | Content Security Policy |
|---|---|---|
| Уровень | Сервер | Браузер пользователя |
| От чего | Автоматические запросы, перебор паролей, спам | Выполнение чужого кода, инжект скриптов, кликджекинг |
| Где настраивается | Middleware, сервер, облачный WAF | HTTP-заголовок, фреймворк, веб-сервер |
| Что контролирует | Кто и как часто стучится к API | Откуда браузер может загружать ресурсы |
Поэтому CSP нельзя считать заменой защите от ботов, и наоборот. Если ты уже настроил rate limiting или капчу, это отлично, но без CSP злоумышленник, нашедший XSS, всё равно сможет атаковать посетителей твоего сайта.
Основные директивы коротко
Правила в CSP называются директивами. Каждая директива отвечает за свой тип ресурсов. Не нужно запоминать их все наизусть, но базовый набор встречается в каждом проекте.
default-src
Это директива по умолчанию. Если для какого-то типа ресурсов не задана своя директива, браузер использует правила из default-src. Если написать:
Content-Security-Policy: default-src 'self'
то все ресурсы — скрипты, стили, картинки, шрифты, медиа, фреймы — должны загружаться только с твоего домена. Это удобная отправная точка, но на практике почти всегда приходится добавлять исключения.
script-src
Контролирует источники JavaScript. Именно эта директива чаще всего спасает от XSS. Пример:
Content-Security-Policy: script-src 'self' https://mc.yandex.ru
Здесь разрешены скрипты с твоего домена и скрипты Яндекс Метрики. Все остальные источники, включая inline-скрипты, будут заблокированы.
style-src
Отвечает за таблицы стилей CSS. Если не указать явно, наследует default-src. Пример:
Content-Security-Policy: style-src 'self' https://fonts.googleapis.com 'unsafe-inline'
Важно: 'unsafe-inline' для стилей ослабляет защиту, но иногда нужен для корректной работы некоторых библиотек. Лучше избегать его без необходимости.
connect-src
Определяет, куда страница может отправлять сетевые запросы из JavaScript: fetch, XMLHttpRequest, WebSocket, EventSource. Это критично для проектов с API и внешними базами данных. Например:
Content-Security-Policy: connect-src 'self' https://abcdefgh123456.supabase.co
Такая запись разрешает запросы к твоему домену и к проекту Supabase. Без этого диапазона игра не сможет сохранять рекорды в общую таблицу. Про то, что такое API и как фронтенд общается с сервером, читай в статье «Что такое API».
img-src
Задаёт, откуда можно загружать изображения. По умолчанию наследует default-src.
Content-Security-Policy: img-src 'self' https://cdn.example.com data:
В примере разрешены картинки с собственного домена, с CDN и встроенные изображения в формате data URI.
font-src
Контролирует загрузку шрифтов. Часто нужен, если ты используешь Google Fonts или другие внешние шрифты.
Content-Security-Policy: font-src 'self' https://fonts.gstatic.com
frame-ancestors
Решает, кто может встраивать твой сайт в <iframe>. Значение 'none' означает полный запрет, 'self' — разрешено только своему домену.
Content-Security-Policy: frame-ancestors 'self'
Это один из лучших способов защититься от кликджекинга.
frame-src
Отвечает за то, какие внешние страницы может встраивать твой сайт. Например, если ты встраиваешь видео с YouTube, нужно явно разрешить домен YouTube.
Content-Security-Policy: frame-src https://www.youtube.com
media-src и object-src
media-src контролирует аудио и видео, object-src — устаревшие плагины вроде Flash, Java-апплетов и ActiveX. Для современных сайтов часто рекомендуют сразу ставить object-src 'none', поскольку плагины давно считаются небезопасными.
Ключевые ключевые слова
Помимо доменов, в директивах используются специальные ключевые слова. В англоязычных запросах их часто ищут как content security policy self, content security policy script src, content security policy frame ancestors и content security policy directive вообще — все эти сочетания сводятся к конкретным директивам и ключевым словам из таблицы ниже.
| Ключевое слово | Значение |
|---|---|
'self' |
Разрешены ресурсы с того же источника, что и страница |
'none' |
Полный запрет на данный тип ресурсов |
'unsafe-inline' |
Разрешает inline-скрипты и inline-стили. Снижает защиту |
'unsafe-eval' |
Разрешает eval, setTimeout со строкой и подобные конструкции |
'strict-dynamic' |
Позволяет доверенному скрипту динамически загружать другие скрипты |
'report-sample' |
Просит браузер приложить фрагмент кода к отчёту о нарушении |
Самый безопасный подход — начать со 'self', а затем добавлять только те внешние домены, которые реально нужны проекту. Каждое 'unsafe-inline' и 'unsafe-eval' — это дыра, которую следует закрывать, если есть возможность.
Как настроить CSP в проекте курса на Next.js
В учебном проекте курса «Змейка» со временем появляются внешние зависимости: Supabase для таблицы рекордов, Яндекс Метрика для аналитики, Google Fonts для красивых шрифтов. Если ты разворачиваешь игру как приложение на Next.js, CSP удобно задавать прямо в файле next.config.js через массив headers.
Вот пример конфигурации, которая покрывает типичные нужды курса:
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' https://mc.yandex.ru",
"style-src 'self' https://fonts.googleapis.com 'unsafe-inline'",
"font-src 'self' https://fonts.gstatic.com",
"img-src 'self' data: https://informer.yandex.ru",
"connect-src 'self' https://<твой-проект>.supabase.co https://api-metrica.yandex.net",
"frame-ancestors 'self'",
"object-src 'none'",
].join('; '),
},
],
},
];
},
};
module.exports = nextConfig;
Что здесь происходит:
default-src 'self'говорит, что по умолчанию всё берётся с твоего домена.script-srcразрешает собственные скрипты и скрипт Яндекс Метрики.style-srcразрешает собственные стили, Google Fonts и inline-стили, которые иногда генерируют сторонние библиотеки.font-srcразрешает шрифты с твоего домена и с Google Fonts.img-srcразрешает картинки с твоего домена, data URI и счётчики Метрики.connect-srcразрешает запросы к твоему домену, проекту Supabase и аналитическому API Метрики.frame-ancestors 'self'запрещает встраивать твой сайт на чужих страницах.object-src 'none'отключает старые плагины.
Важный момент: вместо <твой-проект>.supabase.co нужно подставить реальный URL проекта из панели Supabase. Он выглядит примерно как https://abcdefghijklmnop.supabase.co, но у каждого студента свой. Точно так же для Метрики стоит проверить актуальные домены в документации Яндекса, потому что со временем они могут меняться.
Если ты используешь другие сервисы — платёжный шлюз, чат-виджет, видеохостинг — их домены тоже нужно добавить в соответствующие директивы. Правило простое: если в консоли появляется ошибка Refused to load, значит, какой-то ресурс не внесён в белый список.
Ещё один тонкий момент: если ты деплоишь приложение на Vercel или другой edge-хостинг, заголовки из next.config.js применяются автоматически при сборке. Но если сайт раздаётся как статика через GitHub Pages или обычный CDN, заголовок CSP нужно будет прописать в настройках веб-сервера, иначе браузер его просто не получит. В таких случаях помогает Cloudflare Pages, Netlify или nginx, где можно задать кастомные HTTP-заголовки.
Content-Security-Policy-Report-Only
При первой настройке CSP рекомендуется использовать не обычный заголовок, а режим только отчётов:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
В этом режиме браузер не блокирует ресурсы, но фиксирует каждое нарушение и отправляет отчёт на указанный адрес. Это позволяет собрать полный список доменов, которые реально используются на сайте, прежде чем включать жёсткую политику. После того как все нарушения устранены, можно заменить Report-Only на полноценный Content-Security-Policy.
В Next.js заголовок Report-Only задаётся точно так же, только ключ будет Content-Security-Policy-Report-Only.
Nonce и хеши для inline-скриптов
Иногда inline-скриптов избежать нельзя: например, Next.js сам может вставлять небольшие фрагменты кода в HTML для гидратации. Вместо того чтобы разрешать все inline-скрипты через 'unsafe-inline', можно использовать nonce или хеши.
Nonce — это случайная строка, которая генерируется на сервере для каждого запроса и добавляется в заголовок и в атрибут скрипта:
<script nonce="random123">/* код */</script>
Content-Security-Policy: script-src 'nonce-random123'
Браузер выполнит только тот inline-скрипт, у которого атрибут nonce совпадает со значением в заголовке. Поскольку nonce меняется каждый раз, злоумышленник не сможет его угадать.
Хеш — это вычисленное по содержимому скрипта значение. Если inline-скрипт не меняется, его хеш можно внести в политику:
Content-Security-Policy: script-src 'sha256-abc123...'
Оба подхода безопаснее 'unsafe-inline', но требуют дополнительной настройки на сервере.
Типичная ошибка новичка: включить строгий CSP и сломать сайт
Самая распространённая проблема при первом знакомстве с Content Security Policy — слишком жёсткая политика, которая блокирует легитимные ресурсы. Ты добавляешь default-src 'self', открываешь сайт и видишь: не работает счётчик Метрики, не подгружаются шрифты Google Fonts, игра не может достучаться до Supabase, а в консоли горит красными строками:
Refused to load the script 'https://mc.yandex.ru/metrika/tag.js' because it violates the following Content Security Policy directive: "script-src 'self'".
Refused to connect to 'https://abcdefghijklmnop.supabase.co/rest/v1/records' because it violates the following Content Security Policy directive: "connect-src 'self'".
Это не значит, что CSP плохой. Это значит, что в белом списке не хватает нужных источников. Правильный порядок действий такой:
- Начни с
Content-Security-Policy-Report-Onlyи собери отчёты о нарушениях. - Добавь в политику все домены, которые сайт использует по делу.
- Убедись, что ничего лишнего не просит разрешения.
- Переведи политику в рабочий режим
Content-Security-Policy.
Частая ловушка — добавить 'unsafe-inline' и 'unsafe-eval' «на всякий случай», чтобы всё заработало. Это почти полностью обесценивает защиту от XSS, потому что злоумышленник сможет внедрить и выполнить inline-скрипт. Лучше потратить время и разобраться, какой именно скрипт или стиль требует исключения, и оформить его через nonce или хеш.
Ещё одна ошибка — забыть про фреймы и внешние виджеты. Если на странице встроена оплата через сторонний сервис или видео с YouTube, нужно добавить домены в frame-src. Иначе пользователь увидит пустое место вместо виджета.
И наконец, не стоит копировать CSP из чужой статьи без проверки. У каждого проекта свой набор сервисов, и чужая политика либо не защитит твой сайт, либо сломает его. Собирай список разрешённых источников из реальных запросов своего приложения.
Как проверить, что CSP настроен и не сломал сайт
После включения политики нужно убедиться в двух вещах: защита работает, а сайт продолжает функционировать. Для этого используются стандартные инструменты браузера и несколько простых проверок.
Консоль разработчика
Открой сайт в Chrome, Firefox или Edge, нажми F12, перейди на вкладку Console и перезагрузи страницу. Если CSP блокирует что-то, ты увидишь сообщения вида:
Refused to load the stylesheet 'https://fonts.googleapis.com/css2?family=...' because it violates the following Content Security Policy directive: "style-src 'self'".
Каждая такая строка — это сигнал, что какой-то ресурс не внесён в белый список. Исправляй политику до тех пор, пока не останется только ожидаемые ошибки.
Вкладка Network
На той же панели разработчика открой вкладку Network, перезагрузи страницу и выбери любой запрос к HTML-документу. В разделе Response Headers должен появиться заголовок Content-Security-Policy или Content-Security-Policy-Report-Only с твоей политикой. Если заголовка нет, значит, сервер его не отдаёт, и настройка не применилась.
Режим Report-Only
Перед боевым включением CSP держи политику в режиме только отчётов хотя бы несколько дней. За это время реальные пользователи и тестовые сценарии соберут полный список нарушений. Просматривай отчёты и добавляй недостающие источники. Только после этого переключай заголовок в обычный Content-Security-Policy.
Проверка на XSS-вектор
Если хочешь убедиться, что CSP реально защищает от инжекта, можно провести контролируемый тест. Например, вставь в комментарий или в поле ввода строку:
<script>alert('xss')</script>
При правильно настроенной политике браузер не выполнит скрипт, и в консоли появится сообщение о блокировке. Конечно, делать такие тесты стоит только на локальной копии или на тестовом стенде, а не на боевом сайте с реальными пользователями.
Онлайн-валидаторы
Есть сервисы, которые анализируют CSP-заголовок и подсказывают, какие директивы лишние, каких не хватает и где политика слишком мягкая. Они полезны на этапе аудита, но не заменяют ручную проверку в браузере.
Проверяй не только главную страницу
CSP обычно одинаков для всего сайта, но разные страницы могут использовать разные ресурсы. Например, на странице оплаты подключается платёжный фрейм, в админке — графики, в блоге — встроенные видео. Пройдись по основным маршрутам и убедись, что везде нет красных ошибок в консоли.
flowchart TB
A["Браузер запрашивает страницу"] --> B["Сервер отдаёт HTML и заголовок CSP"]
B --> C["Браузер находит скрипт или ресурс"]
C --> D{"Источник разрешён правилами CSP?"}
D -->|"Да"| E["Браузер загружает ресурс"]
D -->|"Нет"| F["Браузер блокирует загрузку"]
Частые вопросы
CSP блокирует вообще весь JavaScript?
Нет. CSP блокирует только те скрипты, источник которых не указан в политике, а также inline-скрипты и eval, если явно не разрешены. Все скрипты с твоего домена, из разрешённых CDN и с правильным nonce продолжают работать.
Что делать, если счётчик Яндекс Метрики перестал работать после включения CSP?
Нужно добавить домены Метрики в соответствующие директивы. Как правило, это script-src для загрузки кода счётчика и connect-src для отправки аналитических данных. Проверь актуальные домены в официальной документации Метрики, потому что они могут обновляться.
Обязательно ли перечислять отдельно каждую директиву?
Нет. Директива default-src задаёт правило по умолчанию для всех типов ресурсов. Если для какого-то типа не указана своя директива, браузер берёт значение из default-src. Но на практике почти всегда приходится переопределять script-src, style-src и connect-src, потому что у них разные требования.
Можно ли совсем отказаться от inline-скриптов?
Теоретически да, и это самый безопасный вариант. Но многие фреймворки, включая Next.js, используют небольшие inline-скрипты для первичной инициализации. Вместо разрешения всех inline-скриптов через 'unsafe-inline' лучше использовать nonce или хеши, которые позволяют выполнять только конкретные проверенные фрагменты.
Чем отличается Content-Security-Policy-Report-Only от обычного CSP?
Report-Only — это «мягкий» режим. Браузер фиксирует нарушения политики и отправляет отчёты, но не блокирует ресурсы. Он нужен на этапе настройки, чтобы понять, что реально использует сайт и что может сломаться. Обычный Content-Security-Policy действует жёстко: всё, что не разрешено, блокируется.
CSP защищает от всех XSS-атак?
Нет. CSP не устраняет уязвимость в коде и не заменяет проверку входных данных. Если злоумышленник найдёт способ выполнить код из уже разрешённого источника или обойти политику через разрешённую директиву, CSP не поможет. Но в большинстве случаев CSP сильно снижает последствия XSS и не даёт внедрённому скрипту работать от имени сайта.
Заключение
Content Security Policy — это мощный и при этом относительно простой способ повысить безопасность сайта. Он не требует покупки сервисов, не увеличивает нагрузку на сервер и работает прямо в браузере каждого посетителя. Достаточно один раз составить правильный заголовок, и сайт получит надёжную защиту от внедрения чужого кода, кликджекинга и утечки данных через неожиданные внешние запросы.
Главное правило для новичка — не включать CSP сразу в боевом режиме. Начни с Content-Security-Policy-Report-Only, собери отчёты, добавь в политику только те домены, которые действительно нужны, и только потом переводи заголовок в рабочий режим. Избегай искушения разрешить 'unsafe-inline' и 'unsafe-eval' «на всякий случай»: это почти полностью обнуляет защиту. Если inline-скрипты необходимы, используй nonce или хеши.
Помни, что CSP — это один из элементов безопасности, а не серебряная пуля. Вместе с правильной валидацией входных данных, защитой от ботов и бережным хранением секретов он помогает сделать проект устойчивым к типичным угрозам. Если хочешь освоить весь путь от идеи до защищённого приложения, созданного с помощью ИИ — присоединяйся к практикуму skillmake. Там на каждом шаге разбирается не только код, но и безопасность, которую стоит закладывать в проект с самого начала.
Чек-лист «CSP настроен правильно»
- Заголовок
Content-Security-Policyотдаётся сервером для всех страниц. - Политика начиналась с режима
Report-Only, а в рабочий режим переведена после проверки. - Все внешние домены проекта — Supabase, Метрика, шрифты, видео, платёжные виджеты — добавлены в нужные директивы.
- В консоли разработчика нет красных ошибок
Refused to loadна обычных страницах. - Inline-скрипты разрешены не через
'unsafe-inline', а через nonce или хеши. - Директивы
frame-ancestorsиobject-src 'none'настроены осознанно. - Проверены основные сценарии: главная страница, личный кабинет, оплата, админка, встроенный контент.
- Тест на внедрение
<script>alert('xss')</script>не приводит к выполнению кода. - Отчёты о нарушениях регулярно просматриваются и используются для улучшения политики.
После каждого крупного обновления сайта или подключения новой библиотеки перепроверяй политику: свежий виджет, новый шрифт или другая аналитика могут потребовать новых записей в CSP.
Источники
- MDN — «Content Security Policy (CSP)»: developer.mozilla.org/en-US/docs/Web/HTTP/CSP
- Next.js — «headers» в
next.config.js: nextjs.org - Web.dev — «Content Security Policy»: web.dev/articles/csp
Читай дальше
Все статьиНе просто статьи — тебя доведут до результата
В практикуме за 2499 ₽ рядом живая команда практикующих разработчиков и маркетологов: ведём по шагам до твоего работающего приложения. Не «ролики и сам разбирайся» — помогаем на каждом затыке.
Перейти к практикуму