Перелік дозволених доменів у script-src (script-src 'self' https://cdn.example.com) легко обходиться: на дозволеному CDN знайдеться бібліотека, яку можна використати для виконання довільного коду, або JSONP-ендпойнт. Тому сучасна рекомендація - строга CSP на основі nonce чи хешів.
Nonce - випадкове значення, нове для кожної відповіді. Скрипт виконується, лише якщо має той самий nonce, що й заголовок:
Content-Security-Policy: script-src 'nonce-R4nd0mV4lue' 'strict-dynamic'; object-src 'none'; base-uri 'none'
<script nonce="R4nd0mV4lue" src="/build/app.js"></script>
<script nonce="R4nd0mV4lue">window.App = {...}</script>
Скрипт, вставлений через XSS, nonce не знає - і не виконається.
Хеш - для незмінних інлайнових скриптів: script-src 'sha256-...' з хешем вмісту скрипта. Зручно для статичних сторінок, де nonce генерувати нікому.
'strict-dynamic' - довіра поширюється на скрипти, які завантажив уже дозволений (з nonce) скрипт. Потрібно для сучасних збірок: застосунок динамічно підвантажує частини (import()), а знати їхні адреси заздалегідь неможливо. Браузери з підтримкою strict-dynamic ігнорують перелік доменів і 'self' у script-src.
Laravel і Vite. У middleware - згенерувати nonce, передати Vite і поставити заголовок:
public function handle(Request $request, Closure $next): Response
{
$nonce = Vite::useCspNonce(); // генерує випадковий nonce, якщо не передано свій
return $next($request)->withHeaders([
'Content-Security-Policy' => "script-src 'nonce-{$nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none'",
]);
}
Після цього @vite додає nonce до всіх згенерованих тегів <script> і <link>. Для власних інлайнових скриптів - nonce="{{ Vite::cspNonce() }}".
Пастки:
- nonce має бути новим на кожну відповідь. Сторінка з nonce, закешована на CDN, віддає всім той самий nonce - захист зникає;
- обробники в атрибутах (
onclick="...") nonce не підтримують - їх переносять у скрипти; - Livewire і Alpine обчислюють вирази директив через
new Function(), що потребує'unsafe-eval'. Без нього - CSP-збірка Alpine (у Livewire 4 - опціяcsp_safe), яка обмежує складні вирази в директивах.