Content Security Policy
Content Security Policy, или CSP, — это HTTP-заголовок, который говорит браузеру, откуда странице можно загружать скрипты, стили, картинки и куда можно делать сетевые запросы.
Для статического сайта CSP особенно полезна: если сайт не должен обращаться наружу, это можно явно запретить.
Базовая политика для статического сайта
Content-Security-Policy: default-src 'self'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'; object-src 'none'; connect-src 'none'; img-src 'self' data:; script-src 'self'; style-src 'self'; font-src 'self'
Это строгий и понятный профиль для сайта, где:
- нет внешних шрифтов;
- нет CDN;
- нет аналитики;
- нет форм;
- нет runtime-запросов через
fetch; - CSS/JS лежат локально.
Что значит каждая директива
| Директива | Смысл |
|---|---|
default-src 'self' | По умолчанию разрешён только текущий origin |
base-uri 'self' | Ограничивает использование тега <base> |
form-action 'none' | Запрещает отправку форм |
frame-ancestors 'none' | Запрещает встраивание страницы во frame |
object-src 'none' | Запрещает старые object/embed-подключения |
connect-src 'none' | Запрещает runtime-сетевые запросы |
img-src 'self' data: | Разрешает локальные картинки и data URI |
script-src 'self' | Разрешает только локальные скрипты |
style-src 'self' | Разрешает только локальные стили |
font-src 'self' | Разрешает только локальные шрифты |
Почему connect-src 'none'
Если сайт не должен делать runtime-запросы, лучше явно запретить их.
Это блокирует:
fetch;XMLHttpRequest;- WebSocket;
- EventSource;
- некоторые beacon-запросы.
Для локальных утилит это нормально, если вся логика работает в браузере без сети.
Почему без inline-скриптов
Строгая CSP без 'unsafe-inline' не любит inline JS и inline CSS.
Лучше держать скрипты и стили отдельными локальными файлами:
/assets/*.js
/assets/*.css
Так проще проверять, кэшировать и контролировать поведение сайта.
Пример Nginx
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'; object-src 'none'; connect-src 'none'; img-src 'self' data:; script-src 'self'; style-src 'self'; font-src 'self'" always;
Важно использовать always, чтобы заголовок был и на 404, и на других нестандартных ответах.
Проверка через curl
curl -kI https://getsrv.app/ | grep -i content-security-policy
Ожидаемо:
content-security-policy: default-src 'self'; ...
Проверить сразу основные security headers:
curl -kI https://getsrv.app/ \
| grep -Ei 'content-security-policy|x-frame-options|x-content-type-options|referrer-policy|permissions-policy'
Проверка в браузере
Откройте DevTools → Console.
Нормально:
- нет CSP errors;
- нет ошибок загрузки CSS/JS;
- нет запросов к сторонним origin.
Если CSP что-то блокирует, браузер обычно пишет, какая директива сработала.
Частые ошибки
Ошибка 1. Добавили внешний шрифт
Например:
<link href="https://fonts.example.com/font.css" rel="stylesheet">
С текущей CSP это будет заблокировано. Для автономного сайта лучше не подключать внешние шрифты.
Ошибка 2. Добавили inline script
Например:
<script>
console.log('hello')
</script>
С script-src 'self' такой скрипт будет заблокирован. Лучше вынести его в локальный файл.
Ошибка 3. Утилита начала делать fetch
Если инструмент начал обращаться к API, connect-src 'none' заблокирует запрос. Для этого сайта лучше сохранять правило: утилиты работают локально и ничего не отправляют наружу.
Ошибка 4. CSP есть на 200, но нет на 404
Проверьте, что в Nginx используется always:
add_header Content-Security-Policy "..." always;
Когда политику можно расширять
Расширять CSP стоит только под конкретную задачу.
Примеры:
- нужны локальные картинки из
/assets/— уже работает черезimg-src 'self'; - нужен внешний API — добавить конкретный origin в
connect-src; - нужен внешний шрифт — добавить конкретный origin в
font-srcиstyle-src.
Не стоит добавлять:
script-src *
style-src *
connect-src *
Так CSP теряет смысл.
Минимальная проверка после изменения CSP
curl -kI https://getsrv.app/ | grep -i content-security-policy
curl -kI https://getsrv.app/no-such-page | grep -i content-security-policy
Потом открыть сайт в браузере и проверить Console.
Если Console чистая, CSS/JS загружаются, а Network не показывает сторонние runtime-запросы — CSP работает как задумано.