From 774cda12f9c9234c08607fa0caca1b3e52ba3fe4 Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Fri, 20 Mar 2026 15:35:58 +0530 Subject: [PATCH 01/12] Add granular nonces --- app/Config/ContentSecurityPolicy.php | 10 ++++ system/HTTP/ContentSecurityPolicy.php | 56 +++++++++++++++++-- .../system/HTTP/ContentSecurityPolicyTest.php | 32 +++++++++++ 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/app/Config/ContentSecurityPolicy.php b/app/Config/ContentSecurityPolicy.php index f64a9af22b0a..01cb8ec8c918 100644 --- a/app/Config/ContentSecurityPolicy.php +++ b/app/Config/ContentSecurityPolicy.php @@ -199,6 +199,16 @@ class ContentSecurityPolicy extends BaseConfig */ public $sandbox; + /** + * Enable nonce to style tags? + */ + public bool $enableStyleNonce = true; + + /** + * Enable nonce to script tags? + */ + public bool $enableScriptNonce = true; + /** * Nonce placeholder for style tags. */ diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index c94fed4c8e73..78a25855ae2b 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -298,6 +298,20 @@ class ContentSecurityPolicy */ protected $scriptNonce; + /** + * Whether to enable nonce to style-src and style-src-elem directives or not. + * + * @var bool + */ + protected $enableStyleNonce = true; + + /** + * Whether to enable nonce to script-src and script-src-elem directives or not. + * + * @var bool + */ + protected $enableScriptNonce = true; + /** * Nonce placeholder for style tags. * @@ -399,10 +413,13 @@ public function getStyleNonce(): string { if ($this->styleNonce === null) { $this->styleNonce = base64_encode(random_bytes(12)); - $this->addStyleSrc('nonce-' . $this->styleNonce); - if ($this->styleSrcElem !== []) { - $this->addStyleSrcElem('nonce-' . $this->styleNonce); + if ($this->enableStyleNonce) { + $this->addStyleSrc('nonce-' . $this->styleNonce); + + if ($this->styleSrcElem !== []) { + $this->addStyleSrcElem('nonce-' . $this->styleNonce); + } } } @@ -416,10 +433,13 @@ public function getScriptNonce(): string { if ($this->scriptNonce === null) { $this->scriptNonce = base64_encode(random_bytes(12)); - $this->addScriptSrc('nonce-' . $this->scriptNonce); - if ($this->scriptSrcElem !== []) { - $this->addScriptSrcElem('nonce-' . $this->scriptNonce); + if ($this->enableScriptNonce) { + $this->addScriptSrc('nonce-' . $this->scriptNonce); + + if ($this->scriptSrcElem !== []) { + $this->addScriptSrcElem('nonce-' . $this->scriptNonce); + } } } @@ -868,6 +888,30 @@ public function addReportingEndpoints(array $endpoint): static return $this; } + /** + * Enables or disables adding nonces to style-src and style-src-elem directives. + * + * @return $this + */ + public function setEnableStyleNonce(bool $value = true): static + { + $this->enableStyleNonce = $value; + + return $this; + } + + /** + * Enables or disables adding nonces to script-src and script-src-elem directives. + * + * @return $this + */ + public function setEnableScriptNonce(bool $value = true): static + { + $this->enableScriptNonce = $value; + + return $this; + } + /** * DRY method to add an string or array to a class property. * diff --git a/tests/system/HTTP/ContentSecurityPolicyTest.php b/tests/system/HTTP/ContentSecurityPolicyTest.php index e4a5cfc45ac7..55b6071f43b4 100644 --- a/tests/system/HTTP/ContentSecurityPolicyTest.php +++ b/tests/system/HTTP/ContentSecurityPolicyTest.php @@ -731,6 +731,22 @@ public function testBodyScriptNonce(): void $this->assertStringContainsString('nonce-', $header); } + public function testDisabledScriptNonce(): void + { + $this->csp->clearDirective('script-src'); + + $this->csp->setEnableScriptNonce(false); + $this->csp->addScriptSrc('self'); + $this->csp->addScriptSrc('cdn.cloudy.com'); + + $this->assertTrue($this->work()); + + $header = $this->response->getHeaderLine('Content-Security-Policy'); + + $this->assertStringContainsString("script-src 'self' cdn.cloudy.com", $header); + $this->assertStringNotContainsString("script-src 'self' cdn.cloudy.com nonce-", $header); + } + public function testBodyScriptNonceCustomScriptTag(): void { $config = new CSPConfig(); @@ -810,6 +826,22 @@ public function testBodyStyleNonce(): void $this->assertStringContainsString('nonce-', $header); } + public function testDisabledStyleNonce(): void + { + $this->csp->clearDirective('style-src'); + + $this->csp->setEnableStyleNonce(false); + $this->csp->addStyleSrc('self'); + $this->csp->addStyleSrc('cdn.cloudy.com'); + + $this->assertTrue($this->work()); + + $header = $this->response->getHeaderLine('Content-Security-Policy'); + + $this->assertStringContainsString("style-src 'self' cdn.cloudy.com", $header); + $this->assertStringNotContainsString("style-src 'self' cdn.cloudy.com nonce-", $header); + } + public function testBodyStyleNonceCustomStyleTag(): void { $config = new CSPConfig(); From 65b5bff6626af72c6e864fd722972e2b02b523ef Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Wed, 1 Apr 2026 15:13:09 +0530 Subject: [PATCH 02/12] Stop nonces from being added in HTML --- system/HTTP/ContentSecurityPolicy.php | 43 +++++++++++++------ .../system/HTTP/ContentSecurityPolicyTest.php | 10 ++++- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index 78a25855ae2b..709a1f4f3a00 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -411,15 +411,17 @@ public function enabled(): bool */ public function getStyleNonce(): string { + if (! $this->enableStyleNonce) { + $this->styleNonce = null; + return ''; + } + if ($this->styleNonce === null) { $this->styleNonce = base64_encode(random_bytes(12)); + $this->addStyleSrc('nonce-' . $this->styleNonce); - if ($this->enableStyleNonce) { - $this->addStyleSrc('nonce-' . $this->styleNonce); - - if ($this->styleSrcElem !== []) { - $this->addStyleSrcElem('nonce-' . $this->styleNonce); - } + if ($this->styleSrcElem !== []) { + $this->addStyleSrcElem('nonce-' . $this->styleNonce); } } @@ -431,15 +433,17 @@ public function getStyleNonce(): string */ public function getScriptNonce(): string { + if (! $this->enableScriptNonce) { + $this->scriptNonce = null; + return ''; + } + if ($this->scriptNonce === null) { $this->scriptNonce = base64_encode(random_bytes(12)); + $this->addScriptSrc('nonce-' . $this->scriptNonce); - if ($this->enableScriptNonce) { - $this->addScriptSrc('nonce-' . $this->scriptNonce); - - if ($this->scriptSrcElem !== []) { - $this->addScriptSrcElem('nonce-' . $this->scriptNonce); - } + if ($this->scriptSrcElem !== []) { + $this->addScriptSrcElem('nonce-' . $this->scriptNonce); } } @@ -963,7 +967,20 @@ protected function generateNonces(ResponseInterface $response) return ''; } - $nonce = $match[0] === $this->styleNonceTag ? $this->getStyleNonce() : $this->getScriptNonce(); + if ($match[0] === $this->styleNonceTag) { + if (! $this->enableStyleNonce) { + return ''; + } + + $nonce = $this->getStyleNonce(); + } else { + if (! $this->enableScriptNonce) { + return ''; + } + + $nonce = $this->getScriptNonce(); + } + $attr = 'nonce="' . $nonce . '"'; return $jsonEscape ? str_replace('"', '\\"', $attr) : $attr; diff --git a/tests/system/HTTP/ContentSecurityPolicyTest.php b/tests/system/HTTP/ContentSecurityPolicyTest.php index 55b6071f43b4..8740018463f7 100644 --- a/tests/system/HTTP/ContentSecurityPolicyTest.php +++ b/tests/system/HTTP/ContentSecurityPolicyTest.php @@ -739,9 +739,12 @@ public function testDisabledScriptNonce(): void $this->csp->addScriptSrc('self'); $this->csp->addScriptSrc('cdn.cloudy.com'); - $this->assertTrue($this->work()); + $this->assertTrue($this->work('')); $header = $this->response->getHeaderLine('Content-Security-Policy'); + $body = $this->response->getBody(); + + $this->assertStringNotContainsString('nonce=', $body); $this->assertStringContainsString("script-src 'self' cdn.cloudy.com", $header); $this->assertStringNotContainsString("script-src 'self' cdn.cloudy.com nonce-", $header); @@ -834,9 +837,12 @@ public function testDisabledStyleNonce(): void $this->csp->addStyleSrc('self'); $this->csp->addStyleSrc('cdn.cloudy.com'); - $this->assertTrue($this->work()); + $this->assertTrue($this->work("")); $header = $this->response->getHeaderLine('Content-Security-Policy'); + $body = $this->response->getBody(); + + $this->assertStringNotContainsString('nonce=', $body); $this->assertStringContainsString("style-src 'self' cdn.cloudy.com", $header); $this->assertStringNotContainsString("style-src 'self' cdn.cloudy.com nonce-", $header); From b6c7bf5d9b3d790141172005b2f4fcf051f5da66 Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Wed, 1 Apr 2026 15:33:39 +0530 Subject: [PATCH 03/12] Add getter method and change helper methods according to new behaviour --- system/Common.php | 4 ++-- system/HTTP/ContentSecurityPolicy.php | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/system/Common.php b/system/Common.php index 04115993ef87..9da60952934b 100644 --- a/system/Common.php +++ b/system/Common.php @@ -335,7 +335,7 @@ function csp_style_nonce(): string { $csp = service('csp'); - if (! $csp->enabled()) { + if (! $csp->styleNonceEnabled()) { return ''; } @@ -351,7 +351,7 @@ function csp_script_nonce(): string { $csp = service('csp'); - if (! $csp->enabled()) { + if (! $csp->scriptNonceEnabled()) { return ''; } diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index 709a1f4f3a00..111cb131cb6e 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -406,6 +406,22 @@ public function enabled(): bool return $this->CSPEnabled; } + /** + * Whether adding nonce in style-* directives is enabled or not. + */ + public function styleNonceEnabled(): bool + { + return $this->enabled() && $this->enableStyleNonce; + } + + /** + * Whether adding nonce in script-* directives is enabled or not. + */ + public function scriptNonceEnabled(): bool + { + return $this->enabled() && $this->enableScriptNonce; + } + /** * Get the nonce for the style tag. */ From bdffd55577cc869699548fab19f7dd00e5458ec2 Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Wed, 1 Apr 2026 15:34:00 +0530 Subject: [PATCH 04/12] Updated documentation --- user_guide_src/source/outgoing/csp.rst | 15 +++++++++++++++ user_guide_src/source/outgoing/csp/016.php | 14 ++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 user_guide_src/source/outgoing/csp/016.php diff --git a/user_guide_src/source/outgoing/csp.rst b/user_guide_src/source/outgoing/csp.rst index 0c62f3cee56d..d4abe45aa39e 100644 --- a/user_guide_src/source/outgoing/csp.rst +++ b/user_guide_src/source/outgoing/csp.rst @@ -171,3 +171,18 @@ In this case, you can use the functions, :php:func:`csp_script_nonce()` and :php + +.. _csp-control-nonce-generation: + +Control Nonce Generation +==================== + +.. versionadded:: 4.8.0 + +By default, both the script and style nonces are generated automatically. If you want to only generate one of them, +you can set ``$enableStyleNonce`` or ``$enableScriptNonce`` to false in **app/Config/ContentSecurityPolicy.php**: + +.. literalinclude:: csp/016.php + +By setting one of these to false, the corresponding nonce will not be generated, and the placeholder will be replaced with an empty string. +This gives you the flexibility to use nonces for only one type of content if you choose, without affecting the other. diff --git a/user_guide_src/source/outgoing/csp/016.php b/user_guide_src/source/outgoing/csp/016.php new file mode 100644 index 000000000000..b4063e2f4390 --- /dev/null +++ b/user_guide_src/source/outgoing/csp/016.php @@ -0,0 +1,14 @@ + Date: Wed, 1 Apr 2026 15:35:33 +0530 Subject: [PATCH 05/12] cs fix --- system/HTTP/ContentSecurityPolicy.php | 4 +++- tests/system/HTTP/ContentSecurityPolicyTest.php | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index 111cb131cb6e..5006ec81dffd 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -429,6 +429,7 @@ public function getStyleNonce(): string { if (! $this->enableStyleNonce) { $this->styleNonce = null; + return ''; } @@ -451,6 +452,7 @@ public function getScriptNonce(): string { if (! $this->enableScriptNonce) { $this->scriptNonce = null; + return ''; } @@ -997,7 +999,7 @@ protected function generateNonces(ResponseInterface $response) $nonce = $this->getScriptNonce(); } - $attr = 'nonce="' . $nonce . '"'; + $attr = 'nonce="' . $nonce . '"'; return $jsonEscape ? str_replace('"', '\\"', $attr) : $attr; }, $body); diff --git a/tests/system/HTTP/ContentSecurityPolicyTest.php b/tests/system/HTTP/ContentSecurityPolicyTest.php index 8740018463f7..fe17363d41cf 100644 --- a/tests/system/HTTP/ContentSecurityPolicyTest.php +++ b/tests/system/HTTP/ContentSecurityPolicyTest.php @@ -742,7 +742,7 @@ public function testDisabledScriptNonce(): void $this->assertTrue($this->work('')); $header = $this->response->getHeaderLine('Content-Security-Policy'); - $body = $this->response->getBody(); + $body = $this->response->getBody(); $this->assertStringNotContainsString('nonce=', $body); @@ -837,10 +837,10 @@ public function testDisabledStyleNonce(): void $this->csp->addStyleSrc('self'); $this->csp->addStyleSrc('cdn.cloudy.com'); - $this->assertTrue($this->work("")); + $this->assertTrue($this->work('')); $header = $this->response->getHeaderLine('Content-Security-Policy'); - $body = $this->response->getBody(); + $body = $this->response->getBody(); $this->assertStringNotContainsString('nonce=', $body); From 528f1743d972e4a51f9f3fec220faebbf7ef8721 Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Wed, 1 Apr 2026 15:38:23 +0530 Subject: [PATCH 06/12] assertIsString --- tests/system/HTTP/ContentSecurityPolicyTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/system/HTTP/ContentSecurityPolicyTest.php b/tests/system/HTTP/ContentSecurityPolicyTest.php index fe17363d41cf..de9670fdf749 100644 --- a/tests/system/HTTP/ContentSecurityPolicyTest.php +++ b/tests/system/HTTP/ContentSecurityPolicyTest.php @@ -744,6 +744,7 @@ public function testDisabledScriptNonce(): void $header = $this->response->getHeaderLine('Content-Security-Policy'); $body = $this->response->getBody(); + $this->assertIsString($body); $this->assertStringNotContainsString('nonce=', $body); $this->assertStringContainsString("script-src 'self' cdn.cloudy.com", $header); @@ -842,6 +843,7 @@ public function testDisabledStyleNonce(): void $header = $this->response->getHeaderLine('Content-Security-Policy'); $body = $this->response->getBody(); + $this->assertIsString($body); $this->assertStringNotContainsString('nonce=', $body); $this->assertStringContainsString("style-src 'self' cdn.cloudy.com", $header); From b8e7457c194321ce9a0dd37d632df7fc61fcc4a7 Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Wed, 1 Apr 2026 15:47:02 +0530 Subject: [PATCH 07/12] Fix underline thing --- user_guide_src/source/outgoing/csp.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/outgoing/csp.rst b/user_guide_src/source/outgoing/csp.rst index d4abe45aa39e..f6abc8793aa6 100644 --- a/user_guide_src/source/outgoing/csp.rst +++ b/user_guide_src/source/outgoing/csp.rst @@ -175,7 +175,7 @@ In this case, you can use the functions, :php:func:`csp_script_nonce()` and :php .. _csp-control-nonce-generation: Control Nonce Generation -==================== +======================== .. versionadded:: 4.8.0 From 1e66fd654b2bb3a6bf96a187d7c63a79de0555f3 Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Thu, 2 Apr 2026 14:57:54 +0530 Subject: [PATCH 08/12] Added granular CSP nonce handling for rich renderer --- system/Autoloader/Autoloader.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/system/Autoloader/Autoloader.php b/system/Autoloader/Autoloader.php index 1220287845c9..5edbf8f6d643 100644 --- a/system/Autoloader/Autoloader.php +++ b/system/Autoloader/Autoloader.php @@ -460,8 +460,11 @@ private function configureKint(): void } $csp = service('csp'); - if ($csp->enabled()) { + if ($csp->scriptNonceEnabled()) { RichRenderer::$js_nonce = $csp->getScriptNonce(); + } + + if ($csp->styleNonceEnabled()) { RichRenderer::$css_nonce = $csp->getStyleNonce(); } From d9eae25448e61bb5c127bcf48f59999b2c1405af Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Thu, 2 Apr 2026 14:58:24 +0530 Subject: [PATCH 09/12] cs-fix --- system/Autoloader/Autoloader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Autoloader/Autoloader.php b/system/Autoloader/Autoloader.php index 5edbf8f6d643..f4152ebe5b43 100644 --- a/system/Autoloader/Autoloader.php +++ b/system/Autoloader/Autoloader.php @@ -461,7 +461,7 @@ private function configureKint(): void $csp = service('csp'); if ($csp->scriptNonceEnabled()) { - RichRenderer::$js_nonce = $csp->getScriptNonce(); + RichRenderer::$js_nonce = $csp->getScriptNonce(); } if ($csp->styleNonceEnabled()) { From 28e436d99cc9e14011401fba61a6b2718ee7d69d Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Sun, 5 Apr 2026 15:27:57 +0530 Subject: [PATCH 10/12] Add native types --- system/HTTP/ContentSecurityPolicy.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index 5006ec81dffd..ab8a4f30737a 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -303,14 +303,14 @@ class ContentSecurityPolicy * * @var bool */ - protected $enableStyleNonce = true; + protected bool $enableStyleNonce = true; /** * Whether to enable nonce to script-src and script-src-elem directives or not. * * @var bool */ - protected $enableScriptNonce = true; + protected bool $enableScriptNonce = true; /** * Nonce placeholder for style tags. From a2dfe9a41fbbfd3874a889706f05bc9ed1a10798 Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Sun, 5 Apr 2026 15:30:58 +0530 Subject: [PATCH 11/12] cs-fix --- system/HTTP/ContentSecurityPolicy.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index ab8a4f30737a..b674383e5470 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -300,15 +300,11 @@ class ContentSecurityPolicy /** * Whether to enable nonce to style-src and style-src-elem directives or not. - * - * @var bool */ protected bool $enableStyleNonce = true; /** * Whether to enable nonce to script-src and script-src-elem directives or not. - * - * @var bool */ protected bool $enableScriptNonce = true; From 69eb0b12e7e6d0ff35a8ad177cbebc15c18381c4 Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Sun, 5 Apr 2026 15:40:08 +0530 Subject: [PATCH 12/12] Add changelog entry --- user_guide_src/source/changelogs/v4.8.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/user_guide_src/source/changelogs/v4.8.0.rst b/user_guide_src/source/changelogs/v4.8.0.rst index 80c39f3aae6c..e70ef8ddd22e 100644 --- a/user_guide_src/source/changelogs/v4.8.0.rst +++ b/user_guide_src/source/changelogs/v4.8.0.rst @@ -201,6 +201,7 @@ HTTP For example: ``php index.php command -- --myarg`` will pass ``--myarg`` as an argument instead of an option. - ``CLIRequest`` now supports options with values specified using an equals sign (e.g., ``--option=value``) in addition to the existing space-separated syntax (e.g., ``--option value``). This provides more flexibility in how you can pass options to CLI requests. +- Added ``$enableStyleNonce`` and ``$enableScriptNonce`` options to ``Config\App`` to automatically add nonces to control whether to add nonces to style-* and script-* directives in the Content Security Policy (CSP) header when CSP is enabled. See :ref:`csp-control-nonce-generation` for details. Validation ==========