mulertech/csp-bundle
Symfony bundle for managing Content Security Policy headers. Configure policies, nonces, and directives from YAML, then automatically apply CSP headers to responses with support for reporting and per-route rules.
php artisan vendor:publish and pick the CSP config tag. The published config is the single source of truth for which directives are emitted and in which mode (enforcing vs Content-Security-Policy-Report-Only).bootstrap/app.php (Laravel 11+) or app/Http/Kernel.php so every HTML response carries the header. Registering it per-route is possible but easy to forget on new routes.CspHeaderBuilder::build() — it is where the policy string is assembled, and where the new optional plainLoopbackOrigin argument lives in v3.2.0.php artisan serve, open DevTools → Network → the document request, and confirm the Content-Security-Policy header matches your config. Then open the same page in Safari to sanity-check the loopback behaviour that v3.2.0 fixes.BuildCspHeaderEvent class next; it is the supported escape hatch for fully custom policies.Config-driven baseline, event-driven exceptions. Keep the common policy declarative in the config file and reserve a BuildCspHeaderEvent listener for the handful of routes that genuinely need a different policy (embedded checkout iframes, a legacy admin panel). Note that a listener replaces the built policy wholesale — v3.2.0 confirms the listener's policy is sent exactly as written, with no post-processing merge.
Nonces for inline scripts. The usual pattern is to generate a per-request nonce in the middleware, expose it to views (e.g. via a view share or a Blade directive), and let the builder include 'nonce-…' in script-src. Keep the nonce out of any response that could be cached.
Rolling out safely. Start with report-only mode in staging, collect violations, then flip to enforcing per environment. Doing this through config rather than code keeps the diff small and auditable.
Local development exemption (new in v3.2.0). On plain HTTP to a loopback host, the builder now omits upgrade-insecure-requests automatically, so Safari no longer forces assets to HTTPS on localhost, *.localhost, 127.0.0.0/8 or ::1. You get this for free, but if you call CspHeaderBuilder::build() yourself outside the middleware, pass the new plainLoopbackOrigin argument explicitly to opt in.
Dev servers under another name. app.test, myapp.lan or a Homestead/Valet-style hostname are not loopback and keep the directive. For those, disable it in the dev environment only:
// config/csp.php
'upgrade-insecure-requests' => false, // under when@dev
Testing. Assert the header in feature tests ($response->assertHeader('Content-Security-Policy')) and add a targeted test for the loopback case if you rely on the automatic exemption. A test that hits an app.test-style host will legitimately not get the exemption — don't "fix" that by blanket-disabling the directive.
upgrade-insecure-requests. That is intentional; don't treat the missing exemption there as a bug.trusted_proxies, the framework believes the request is plain HTTP. v3.2.0 deliberately keeps the directive in that case because the host is public — but you should still configure trusted_proxies so scheme detection (and generated URLs) is correct.BuildCspHeaderEvent means the builder's output — including any directives you set in config — is discarded. Keep listener logic close to the config it shadows, or you will chase directives that "mysteriously disappeared".upgrade-insecure-requests: false under when@dev (as the README recommends) is the safe shape; a bare top-level false silently weakens every environment.BuildCspHeaderEvent for total control, and the plainLoopbackOrigin argument for direct builder calls in artisan commands or custom middleware.How can I help you explore Laravel packages today?