Search by

oliweb / statamic-csp-nonce

oliweb

Statamic addon that preserves CSP nonces through static caching (half/application driver)

Package info

github.com/oliweb-ch/statamic-csp-nonce

Type:statamic-addon

pkg:composer/oliweb/statamic-csp-nonce

Statistics

Installs: 5

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.0.0 2026-09-04 12:35 UTC

This package is auto-updated.

Last update: 2026-09-05 16:01:02 UTC


README

Tests

A Statamic addon that preserves Content Security Policy (CSP) nonces through the static cache (half/application driver).

The Problem

Statamic's half static cache stores rendered HTML. If that HTML contains nonce="abc123" attributes (required for a strict script-src or style-src CSP), those nonces are frozen in the cache. On the next request a new nonce is generated, the cached one no longer matches the CSP header, and the browser blocks your scripts and styles.

The Solution

This addon registers a Replacer that:

  1. Before caching – swaps the nonce with an internal placeholder.
  2. On cache hit – swaps the placeholder back with the fresh nonce for the current request.

No configuration required. No extra dependencies.

Requirements

  • PHP ^8.3
  • Statamic ^6
  • Static cache driver set to application (half mode)

Installation

composer require oliweb/statamic-csp-nonce

The service provider is auto-discovered. Nothing else to do.

Convention

Your CSP middleware must share the nonce via the View factory:

view()->share('csp_nonce', $nonce);

That is the only contract between your application and this package. The package does not generate nonces, does not write CSP headers, and does not define any directives — all of that stays in your middleware.

Minimal middleware example

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class AddCspHeaders
{
    public function handle(Request $request, Closure $next): Response
    {
        $nonce = base64_encode(random_bytes(16));

        view()->share('csp_nonce', $nonce);

        $response = $next($request);

        $csp = "default-src 'self'; "
             . "script-src 'self' 'nonce-{$nonce}'; "
             . "style-src 'self' 'nonce-{$nonce}';";

        $response->headers->set('Content-Security-Policy', $csp);

        return $response;
    }
}

Register it in bootstrap/app.php (Laravel 11+):

->withMiddleware(function (Middleware $middleware) {
    $middleware->web(append: [
        \App\Http\Middleware\AddCspHeaders::class,
    ]);
})

Using the nonce in Blade

<script nonce="{{ view()->shared('csp_nonce') }}">
    // your inline script
</script>

<style nonce="{{ view()->shared('csp_nonce') }}">
    /* your inline style */
</style>

Or share it as a global helper in AppServiceProvider:

Blade::directive('cspNonce', fn () => "<?php echo view()->shared('csp_nonce'); ?>");
<script nonce="@cspNonce">...</script>

How It Works

The addon auto-registers Oliweb\StatamicCspNonce\CspNonceReplacer into statamic.static_caching.replacers at boot time, after the config is loaded. Existing replacers (CSRF token, no-cache fragments) are preserved.

License

MIT