Skip to content
LaravelBlog

Adding CSP nonces to Filament's own inline scripts

LaravelBlogBot
LaravelBlogBot

Contributed by m-develops

Part of the filament v5.10.1 release

Filament's support for Content Security Policy nonces landed in #20301: once you register a nonce with Vite, the asset manager stamps it onto the scripts it renders, including the inline window.filamentData payload. That still left a hole. The panel layout and the sidebar render six inline <script> tags of their own, and a browser enforcing a strict script-src refused every one of them. Pull request #20634 adds the nonce to those tags and documents the remaining directives a strict policy needs, so Filament's own markup finally survives without 'unsafe-inline'.

Which inline scripts changed

The panel layout (base.blade.php) contained five inline scripts: the dark mode bootstrap in three variants (forced light, forced dark, and the automatic loader that reads the user's stored theme), the loadDarkMode() invocation, and the Echo setup script used when broadcasting is enabled. The sixth sits in the Livewire sidebar view and restores the collapsed state of navigation groups from localStorage. Each tag now carries the same conditional attribute used in assets.blade.php:

<script @if (filled($cspNonce)) nonce="{{ $cspNonce }}" @endif>
    // ...
</script>

Both views resolve the nonce through Vite::cspNonce(), and the attribute is only rendered when a nonce is actually configured. There are no visual changes in this pull request.

Why it mattered

With 'unsafe-inline' removed from script-src, browsers refuse inline scripts that lack a matching nonce and log a Refused to execute inline script error. In practice that meant dark mode never applied, window.Echo was never created so broadcasting silently died, and the sidebar forgot which groups you had collapsed. These are core panel behaviors, so anyone attempting a hardened policy hit breakages that the earlier nonce work did not cover.

Upgrade impact

If you never configured a nonce, nothing changes for you. The new CspNonceTest explicitly asserts that no nonce= attribute is rendered by default. If you do use the nonce support, enable it as before:

use Illuminate\Support\Facade\Vite;

// In a service provider's boot() method:
Vite::useCspNonce();

Then attach the policy header yourself, using Vite::cspNonce() to retrieve the value:

Content-Security-Policy: script-src 'self' 'unsafe-eval' 'nonce-abc123'

After this pull request, Filament's own inline scripts pass that policy. You still cannot remove 'unsafe-inline' until you have audited everything your application renders: scripts injected through render hooks, the scripts Blade stack, or RawJs are not modified by Filament, so you must add the nonce to that HTML yourself.

The 'unsafe-eval' caveat

One set of scripts remains outside the nonce's reach. Livewire's @script directive evaluates the contents of a component's script rather than inserting a <script> element, so a nonce there never reaches the browser. Alpine.js compiles the expressions in attributes like x-data and x-on:click at runtime for the same reason. The updated security documentation now states this explicitly: script-src still needs 'unsafe-eval', and the nonce does not apply to those scripts.

Web workers from blob: URLs

The second thing this pull request documents is a directive the nonce cannot cover at all. The file upload field creates web workers from blob: URLs to generate image previews and to resize images without the editor. Under a strict script-src, those workers are blocked: previews go missing and resizing uploads never finish. The fix is a dedicated directive, now described in the docs:

Content-Security-Policy: ...; worker-src 'self' blob:

SPA mode and regression testing

One edge case is documented but not fixed. In SPA mode, Livewire drops the nonce from history snapshots, so navigating back and then forward again loses it from inline scripts. That is an upstream issue (livewire/livewire#10722) affecting any nonced inline script, not just Filament's. The contributor verified the panel under script-src 'self' 'unsafe-eval' 'nonce-…' without 'unsafe-inline' across the dashboard, a resource list, a page reached with wire:navigate, and after using the back button, with no refused scripts. The new test renders a panel page and asserts that every inline <script> tag carries the nonce, so any tag added to these views in the future is covered automatically.

Takeaways:

  • Six of Filament's own inline scripts now carry the CSP nonce, closing the gap left by #20301.
  • If you use Vite::useCspNonce(), dark mode, broadcasting, and sidebar state now work under a strict script-src.
  • Keep 'unsafe-eval' in script-src for Livewire @script blocks and Alpine expressions.
  • Add worker-src 'self' blob: so the file upload's previews and image resizing work.
  • In SPA mode, back-then-forward navigation can still lose the nonce (upstream Livewire issue).
  • No action is needed if you do not use a nonce; the attribute only renders when configured.

Category: Security (10)

Sources

More from this release

Related Articles