spatie/laravel-csp
Spatie's Laravel CSP package makes it easy to set Content Security Policy headers in Laravel apps with configurable policies and directives. It helps restrict which sites scripts can contact, reducing risks from malicious or compromised JavaScript dependencies.
What the package actually is. A header emitter and policy-expression layer for Laravel: a config file, a middleware, a nonce generator, and ~40 pre-built "preset" classes that encode known third-party origins (Stripe, Hotjar, HubSpot, Google Fonts, etc.). It does not discover your script origins, rewrite inline handlers, or remediate eval() in vendor bundles. For a TPM this distinction drives sizing: package integration is roughly half a day of engineering; the CSP program (origin inventory, inline-script remediation, report triage, staged enforcement) is the real workstream and is measured in weeks for any non-trivial frontend.
Architecture fit. High for a Laravel monolith or modular app. It hooks the framework the way the framework expects: auto-discovered service provider, publishable config/csp.php, global middleware via bootstrap/app.php or route/route-group scoping, and a middleware parameter that lets a route override the configured presets. Presets are single-responsibility classes implementing an interface — a clean extension point that maps well to vendor sprawl. The "policy as config" model is also an audit asset: every origin allowance lands in a reviewable PR, which is exactly the evidence artifact security reviewers ask for under SOC 2 / PCI change-control.
Integration feasibility. Lowest cost class available: composer require, one vendor:publish, one middleware registration. No database, no queue, no mandatory external service — the reporting endpoint is optional. It supports both enforcement and report-only policies in the same config (presets vs report_only_presets, plus directives/report_only_directives), which means one install covers both the audit phase and the enforcement phase. That is the single most valuable property of this package for planning purposes.
Technical risk. Library risk is low; policy risk is the dominant item.
nonce_enabled, or cache-bypass for HTML), not a discovery during load testing.<script> and style block carries the attribute. Templates, CMS-injected markup, and vendor widgets fail silently and are found in production, not staging.enabled_while_hot_reloading defaults to false, so Vite HMR assets are blocked by default. Expect tickets in week one.Key questions to answer before commitment.
report-uri.com, Sentry, or an internal endpoint — and what is the privacy review for URLs captured in reports?Stack fit. Clean if the surface is Laravel. Treat CSP as a two-lane program: lane one is this package for Laravel HTML responses; lane two is edge/nginx header configuration for everything else. A TPM who scopes the package as the whole solution will miss half the estate.
Migration path. The package's dual-policy design gives a natural, reversible path:
Basic in report_only_presets with CSP_REPORT_URI pointed at a collector. Nothing breaks; baseline data starts flowing.report_only_presets into presets, not by disabling report-only. Keep a report-only policy running permanently as a canary for future changes.Compatibility considerations.
bootstrap/app.php form assumes the newer kernel.nonce_enabled = false is a documented escape hatch for legacy areas that need unsafe-inline. Usable, but attach a sunset date or it becomes permanent.Sequencing (recommended).
| Phase | Scope | Duration | Exit criterion |
|---|---|---|---|
| 0. Baseline | Install, report-only Basic, wire collector |
~1 day | Reports arriving in staging and prod |
| 1. Inventory | Triage violations, author custom presets per origin, decide inline strategy | 1–2 weeks | Zero unexplained violations on top 20 pages |
| 2. Staged enforcement | Enforce on low-risk route group → authenticated app → public marketing | 1–2 sprints | No new violation classes; error budget held |
| 3. Global + guardrail | Global middleware; add "new third-party = preset PR" to definition of done; CSP console-error check in regression | Ongoing | Policy changes flow through normal review |
Maintenance. Code churn is near zero; policy churn is continuous. Every new analytics tag, chat widget, or payment SDK is a config change and a PR review. Budget a named owner (security engineer or platform lead) plus a quarterly policy review, and expect the preset file to become a living inventory of the vendor estate — which is genuinely useful documentation, but only if someone maintains it. Upgrades are low-effort but not zero-effort because preset contents change upstream.
Support. No commercial SLA; support is Spatie's OSS issue tracker (paid Spatie support products exist and are worth pricing in if CSP is audit-critical). If you use report-uri.com or similar as the collector, that is a separate third-party dependency with its own DPA and availability profile. Flag for legal/privacy: violation reports contain page URLs and blocked resource URIs, which can include query strings with identifiers; this needs a data-handling review before you point it at a production site with PII in URLs.
Scaling. Runtime cost is negligible — string assembly plus one response header; nonce generation is a cheap per-request operation. The scaling concern is report volume, not request throughput: a busy site in report-only mode generates a large, bursty stream that can flood a naive collector. Rate-limit or sample before enabling reports on high-traffic pages.
Failure modes to plan around.
| Failure | Symptom | Mitigation |
|---|---|---|
| Enforcement without report-only trial | Blank regions, dead buttons, silent JS failures | Report-only gate before every promotion |
| Nonce + page cache | Intermittent script blocking for a subset of users | Decide cache/nonce strategy up front |
| Duplicate CSP from CDN/WAF | Strictest-wins intersection; unexplained breakage | Audit response headers end-to-end before enforcement |
| Inline scripts lacking nonce | Analytics/widgets silently dead | Inventory inline blocks; use hashes or refactor |
| HMR blocked in dev | Local dev pain, pressure to disable package | Flip enabled_while_hot_reloading in local env |
| Ownerless report stream | Reports accumulate, no triage, policy rots | Named owner + triage cadence in runbook |
unsafe-inline escape hatch |
Permanent weakening of the policy | Sunset date recorded with the exception |
Ramp-up. One engineer, under a day to install and validate; the organization needs more: a team briefing (CSP breakage looks like "the site is broken," not "a header is wrong"), a QA checklist step to watch the browser console on release candidates, and a support/CS runbook so frontline staff can recognize and escalate CSP-shaped incidents. Two sprints is a realistic end-to-end estimate for a mid-sized Laravel app with typical marketing-tag sprawl.
Recommendation. Adopt, contingent on three commitments: report-only before enforcement, a named ongoing policy owner, and an explicit decision on nonce-vs-caching. The package removes the plumbing cost of CSP almost entirely; the residual cost sits in inventory and remediation, and that cost is the same regardless of which library you choose — which is precisely why paying half a day for this one is the right trade.
How can I help you explore Laravel packages today?