From a302d13c738357ea077ee72518f3d457dfbda82b Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Fri, 6 Nov 2026 05:34:44 -0500 Subject: [PATCH 1/3] Carry a host's CSP nonce on every inline block of the browser page A host with a nonce-based policy (script-src 'self' 'nonce-...') hands its nonce to Vite. The loader, the frame style, the stylesheet, the module and dev-server scripts now carry it, and a csp-nonce meta tag gives it to the styles React Aria injects. Without a host nonce the package's nonce mode supplies core's, as before. --- CHANGELOG.md | 4 +++ docs/tools/host-integration.md | 12 ++++++++ resources/views/app.blade.php | 19 ++++++++---- src/Support/PageNonce.php | 49 ++++++++++++++++++++++++++++++ src/View/Components/Prepaint.php | 9 +++--- tests/Feature/HostCspNonceTest.php | 36 ++++++++++++++++++++++ tests/Feature/PrepaintTest.php | 18 +++++++++++ 7 files changed, 137 insertions(+), 10 deletions(-) create mode 100644 src/Support/PageNonce.php create mode 100644 tests/Feature/HostCspNonceTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 4687833..6aed3de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to `ichava/icon-browser` follow [Keep a Changelog](https://k ## [Unreleased] +### Fixed + +- **The browser page works under a host's nonce-based CSP.** A host that sends its own policy with a per-request nonce (`script-src 'self' 'nonce-…'`, as ichava.com does) hands that nonce to Vite; the page now puts it on the loader's ` @@ -58,7 +65,7 @@ @if(! $viteDev) - + @endif diff --git a/src/Support/PageNonce.php b/src/Support/PageNonce.php new file mode 100644 index 0000000..dfc036d --- /dev/null +++ b/src/Support/PageNonce.php @@ -0,0 +1,49 @@ +vite->cspNonce(); + + if (is_string($host) && $host !== '') { + return $host; + } + + if (config('ichava.icon-browser.security.csp.mode', 'strict') === 'nonce') { + return $this->nonce->value(); + } + + return null; + } + + /** ` nonce="…"`, ready to put after a tag name, or '' when the page has no nonce. */ + public function attribute(): string + { + $value = $this->value(); + + return $value === null ? '' : ' nonce="' . e($value) . '"'; + } +} diff --git a/src/View/Components/Prepaint.php b/src/View/Components/Prepaint.php index 5774d3e..0da7535 100644 --- a/src/View/Components/Prepaint.php +++ b/src/View/Components/Prepaint.php @@ -6,7 +6,7 @@ use Illuminate\View\Component; use Illuminate\Support\HtmlString; -use Simtabi\Laranail\Ichava\Support\SecurityNonce; +use Simtabi\Laranail\Ichava\IconBrowser\Support\PageNonce; /** * Inline react-icon-browser's static loader, so something paints on the first HTML parse @@ -22,7 +22,7 @@ */ final class Prepaint extends Component { - public function __construct(private readonly SecurityNonce $nonce) {} + public function __construct(private readonly PageNonce $nonce) {} public static function path(): string { @@ -33,8 +33,9 @@ public function render(): HtmlString { $html = is_file(self::path()) ? (string) file_get_contents(self::path()) : ''; - if ($html !== '' && config('ichava.icon-browser.security.csp.mode', 'strict') === 'nonce') { - $attribute = $this->nonce->attribute(); + $attribute = $this->nonce->attribute(); + + if ($html !== '' && $attribute !== '') { $html = str_replace(['