atomic-csp-hardening.md 4.8 KB

CSP Hardening: unsafe-inline и unsafe-eval в Caddyfile

Контекст: В deploy/Caddyfile:40 Content-Security-Policy использует 'unsafe-inline' и 'unsafe-eval' для script-src. Это ослабляет XSS-защиту.

Текущая политика

Content-Security-Policy:
    default-src 'self';
    script-src 'self' 'unsafe-inline' 'unsafe-eval';
    style-src 'self' 'unsafe-inline';
    img-src 'self' data: blob: https://*.basemaps.cartocdn.com;
    font-src 'self' data:;
    connect-src 'self' wss: https://api.{$DOMAIN};
    frame-ancestors 'none';

Почему так

  1. 'unsafe-inline' для скриптов: Next.js использует inline-скрипты для гидратации и данных страницы (__NEXT_DATA__, __NEXT_LOADED_PAGES__ и т.д.)
  2. 'unsafe-eval': Нужен для source maps в development. Next.js 14 также может использовать eval() для некоторых динамических импортов и Webpack HMR.
  3. 'unsafe-inline' для стилей: Tailwind CSS использует JIT-генерацию стилей, которые вставляются через <style>.

Проблема

'unsafe-inline' и 'unsafe-eval' — самые опасные директивы CSP:

  • XSS-атака может выполнить произвольный inline-скрипт в браузере пользователя
  • CSP становится практически бесполезным для защиты от XSS

Решение (поэтапное)

Фаза 1: Переход на nonce-основанную CSP

Next.js 14 поддерживает nonce через middleware:

// frontend/src/middleware.ts
import { NextResponse } from 'next/server'
import crypto from 'crypto'

export function middleware(request: Request) {
    const nonce = crypto.randomBytes(16).toString('base64')
    const csp = [
        `default-src 'self'`,
        `script-src 'self' 'nonce-${nonce}'`,
        `style-src 'self' 'nonce-${nonce}'`,
        `img-src 'self' data: blob: https://*.basemaps.cartocdn.com`,
        `font-src 'self' data:`,
        `connect-src 'self' wss: https://api.{$DOMAIN}`,
        `frame-ancestors 'none'`,
    ].join('; ')
    
    const response = NextResponse.next()
    response.headers.set('Content-Security-Policy', csp)
    response.headers.set('X-Nonce', nonce)
    return response
}

Фаза 2: strict-dynamic

Когда nonce работает, можно перейти на 'strict-dynamic':

script-src 'self' 'nonce-{random}' 'strict-dynamic';

'strict-dynamic' говорит браузеру доверять только скриптам с nonce и скриптам, загруженным этими доверенными скриптами. Все inline-скрипты без nonce блокируются.

Фаза 3: CSP Reporting

Добавить report-uri или report-to для сбора нарушений CSP без блокировки:

Content-Security-Policy-Report-Only:
    ... ;
    report-uri https://example.com/csp-report;

Альтернативы

Подход Уровень защиты Сложность внедрения
Nonce + middleware Высокий Средняя — нужно модифицировать Next.js
strict-dynamic + nonce Очень высокий Высокая — ломает сторонние скрипты
Хеши ('sha256-...') Средний Низкая — но хеши меняются при каждом деплое
Report-Only (начать с этого) Мониторинг Низкая — не блокирует, только логирует

Текущий риск

Для production: XSS через внедрение inline-скрипта. Если злоумышленник найдёт XSS-уязвимость, CSP не остановит его.

Что уже защищает

  • HttpOnly cookie для refresh token
  • SameSite=Strict для CSRF
  • frame-ancestors 'none' — защита от clickjacking
  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY

Треккинг

  • Фаза 1: Middleware с nonce
  • Фаза 2: strict-dynamic
  • Фаза 3: CSP Reporting
  • Удалить unsafe-inline и unsafe-eval

Связанные заметки

  • [[MOC-security-patterns]] — общая карта безопасности
  • [[backend-auth-security]] — HttpOnly cookie
  • [[deploy-production-readiness]] — production hardening
  • [[atomic-csrf-protection]] — CSRF защита

Источник

Code review PhotoPlaces 2026-06. deploy/Caddyfile:40.

Теги

#security #csp #frontend #nextjs #production #hardening