Обновлено:

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 работает как задумано.