jeffersongoncalves/laravel-umami
Laravel Umami integrates privacy-friendly Umami analytics into Laravel apps, with publishable configuration, a tracking script component, and simple helpers for sending pageviews and custom events to self-hosted or cloud Umami instances.
Illuminate\Support\Facades\Vite is available and your layout actually uses @vite.config/<package>.php) to see how the <script> tags are rendered, then the Blade/view or renderer class that emits them. That renderer is the file v3.1.0 changed — it now reads the nonce before outputting nonce="...".resources/views/layouts/app.blade.php, load a page, and view-source. Confirm the <script> tag now carries a nonce attribute only when a nonce has been set for the request.'unsafe-inline'), this release is the upgrade that removes your workaround. Before, you either had to relax script-src or manually post-process the emitted HTML. After, nothing changes in your code — just upgrade and verify.composer update <vendor>/<package>
php artisan vendor:publish --tag=<package>-config # if not already published
Then, in a middleware or service provider that runs before the layout renders:
use Illuminate\Support\Facades\Vite;
Vite::useCspNonce($nonceFromYourCspMiddleware); // or Vite::useCspNonce() to auto-generate
Pattern 1 — Let your CSP middleware own the nonce.
The cleanest day-to-day setup: a single middleware generates the nonce, stores it via Vite::useCspNonce(), and emits the Content-Security-Policy header containing the same value. Every consumer (Vite, this package, Livewire, your own inline scripts) then reads from one source of truth.
// app/Http/Middleware/ContentSecurityPolicy.php
public function handle($request, Closure $next)
{
$nonce = Vite::useCspNonce(); // generates + stores, returns the value
$response = $next($request);
$response->headers->set('Content-Security-Policy',
"script-src 'self' 'nonce-{$nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'self'"
);
return $response;
}
Register it globally so it runs on every request that might render the package's scripts.
Pattern 2 — Roll out in report-only mode first.
Ship Content-Security-Policy-Report-Only with 'nonce-...' and a report-uri, collect violations for a week, then flip to enforcing. This is the safest way to validate that v3.1.0's nonce is landing on every script the package emits — including ones injected on AJAX-driven partials.
Pattern 3 — Keep the nonce flowing into partial/fragment renders.
Because the nonce lives on the Vite singleton for the request, any Blade partial, Livewire component, or AJAX endpoint that renders the package's script in the same request gets the nonce automatically. The moment you return HTML from an endpoint that didn't run the middleware, you lose it — so apply the middleware to API/partial routes too if they render HTML.
Pattern 4 — Assert it in tests. Nonce support is easy to silently regress (someone re-renders the tag with a raw string). Pin it:
Vite::useCspNonce('test-nonce-123');
$this->get('/')
->assertSee('nonce="test-nonce-123"', false)
->assertSee('<script', false);
Add the negative case too: with no nonce set, assert the attribute is absent (or empty) so you don't ship nonce="", which browsers treat as a mismatch.
Pattern 5 — Combine with 'strict-dynamic'.
Once the root script carries a valid nonce, 'strict-dynamic' lets it load its own dependents without host allowlisting. This is where the release pays off: you can drop long script-src https://cdn... lists and let the nonce propagate.
Vite::useCspNonce() must be called in middleware or a service provider that runs before the view composes. Calling it in a view composer that renders after the package's script has already been stringified gives you a header nonce that no tag carries — a guaranteed CSP break with a confusing "blocked" error.Cache::remember() around rendered markup will freeze a nonce and serve it to every subsequent visitor. Browsers reject it and you get intermittent, traffic-dependent CSP failures. Render the script tag outside the cached region, or cache the data and render the tag fresh.nonce="..." nonce="..." or two different values. Grep your app for nonce after upgrading.nonce="" is worse than no nonce. An empty attribute fails the match and blocks the script even under a permissive policy. If the package emits the attribute unconditionally in your version, verify; the intended behavior is to omit it entirely when no nonce is set.style= are unaffected. onclick="..." needs 'unsafe-hashes' plus a hash, and inline styles need 'unsafe-inline'/hashes in style-src. Only <script> elements are covered here — don't assume the release fixes your whole CSP.document.createElement('script')) don't inherit a nonce automatically; the creating script must copy script.nonce across. Test the full load path in a browser console, not just view-source.nonce on every package-emitted <script> → compare byte-for-byte with the 'nonce-...' in the response header → check the browser console's CSP violation for the exact directive and blocked URI. If the tag has a nonce but is still blocked, the header value and the attribute value almost certainly differ (trailing whitespace, base64 padding, or a regenerated nonce).Vite::useCspNonce() behaves differently (or is absent). If you're on Laravel 9/10, verify the method exists and that the package's nonce lookup degrades gracefully rather than throwing.data-nonce for a custom loader), and wrap the renderer in your own Blade component if you want to centralize "always pass the current nonce." Keep the nonce source as Vite::cspNonce() rather than inventing a parallel store — that's what makes third-party packages cooperate.How can I help you explore Laravel packages today?