From 19380aa373054a709f2ddff74112ef9def1690de Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Sun, 8 Feb 2026 18:09:58 +0530 Subject: [PATCH 01/11] Created a method to clear nonce placeholders. --- system/HTTP/ContentSecurityPolicy.php | 5 +++++ system/HTTP/ResponseTrait.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index 04d93365af25..203e867372da 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -1052,4 +1052,9 @@ public function clearDirective(string $directive): void $this->{$this->directives[$directive]} = []; } + + public function clearNoncePlaceholders(string $text): string + { + return str_replace([$this->styleNonceTag, $this->scriptNonceTag], '', $text); + } } diff --git a/system/HTTP/ResponseTrait.php b/system/HTTP/ResponseTrait.php index 211663bc7987..74b9e10c530d 100644 --- a/system/HTTP/ResponseTrait.php +++ b/system/HTTP/ResponseTrait.php @@ -370,7 +370,7 @@ public function send() if ($this->CSP->enabled()) { $this->CSP->finalize($this); } else { - $this->body = str_replace(['{csp-style-nonce}', '{csp-script-nonce}'], '', $this->body ?? ''); + $this->body = $this->CSP->clearNoncePlaceholders($this->body); } $this->sendHeaders(); From 489d2016c433772ecceef72799278c887043caad Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Sun, 8 Feb 2026 18:10:08 +0530 Subject: [PATCH 02/11] Added tests --- .../system/HTTP/ContentSecurityPolicyTest.php | 63 +++++++++++++++ tests/system/HTTP/ResponseTest.php | 76 +++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/tests/system/HTTP/ContentSecurityPolicyTest.php b/tests/system/HTTP/ContentSecurityPolicyTest.php index af9638b6a8ba..6e3c320c1eaf 100644 --- a/tests/system/HTTP/ContentSecurityPolicyTest.php +++ b/tests/system/HTTP/ContentSecurityPolicyTest.php @@ -937,4 +937,67 @@ public function testClearDirective(): void $this->assertNotContains('report-uri http://example.com/csp/reports', $directives); $this->assertNotContains('report-to default', $directives); } + + public function testClearNoncePlaceholdersWithDefaultTags(): void + { + $config = new CSPConfig(); + $csp = new ContentSecurityPolicy($config); + + $body = 'Test {csp-script-nonce} and {csp-style-nonce} here'; + $cleaned = $csp->clearNoncePlaceholders($body); + + $this->assertSame('Test and here', $cleaned); + $this->assertStringNotContainsString('{csp-script-nonce}', $cleaned); + $this->assertStringNotContainsString('{csp-style-nonce}', $cleaned); + } + + public function testClearNoncePlaceholdersWithCustomTags(): void + { + $config = new CSPConfig(); + $config->scriptNonceTag = '{custom-script-nonce}'; + $config->styleNonceTag = '{custom-style-nonce}'; + $csp = new ContentSecurityPolicy($config); + + $body = 'Test {custom-script-nonce} and {custom-style-nonce} here'; + $cleaned = $csp->clearNoncePlaceholders($body); + + $this->assertSame('Test and here', $cleaned); + $this->assertStringNotContainsString('{custom-script-nonce}', $cleaned); + $this->assertStringNotContainsString('{custom-style-nonce}', $cleaned); + } + + public function testClearNoncePlaceholdersWithEmptyBody(): void + { + $config = new CSPConfig(); + $csp = new ContentSecurityPolicy($config); + + $body = ''; + $cleaned = $csp->clearNoncePlaceholders($body); + + $this->assertSame('', $cleaned); + } + + public function testClearNoncePlaceholdersWithNoPlaceholders(): void + { + $config = new CSPConfig(); + $csp = new ContentSecurityPolicy($config); + + $body = 'Test body with no placeholders'; + $cleaned = $csp->clearNoncePlaceholders($body); + + $this->assertSame($body, $cleaned); + } + + public function testClearNoncePlaceholdersWithMultiplePlaceholders(): void + { + $config = new CSPConfig(); + $csp = new ContentSecurityPolicy($config); + + $body = ''; + $cleaned = $csp->clearNoncePlaceholders($body); + + $this->assertStringNotContainsString('{csp-script-nonce}', $cleaned); + $this->assertStringNotContainsString('{csp-style-nonce}', $cleaned); + $this->assertSame('', $cleaned); + } } diff --git a/tests/system/HTTP/ResponseTest.php b/tests/system/HTTP/ResponseTest.php index 84408df0e5a5..9ee3612da492 100644 --- a/tests/system/HTTP/ResponseTest.php +++ b/tests/system/HTTP/ResponseTest.php @@ -577,4 +577,80 @@ public function testPretendOutput(): void $this->assertSame('Happy days', $actual); } + + public function testSendRemovesDefaultNoncePlaceholdersWhenCSPDisabled(): void + { + $config = new App(); + $config->CSPEnabled = false; + + $response = new Response($config); + $response->pretend(true); + + $body = ''; + $response->setBody($body); + + ob_start(); + $response->send(); + $actual = ob_get_contents(); + ob_end_clean(); + + // Nonce placeholders should be removed when CSP is disabled + $this->assertStringNotContainsString('{csp-script-nonce}', $actual); + $this->assertStringNotContainsString('{csp-style-nonce}', $actual); + $this->assertStringContainsString('', $actual); + $this->assertStringContainsString('', $actual); + } + + public function testSendRemovesCustomNoncePlaceholdersWhenCSPDisabled(): void + { + $appConfig = new App(); + $appConfig->CSPEnabled = false; + + // Create custom CSP config with custom nonce tags + $cspConfig = new \Config\ContentSecurityPolicy(); + $cspConfig->scriptNonceTag = '{custom-script-tag}'; + $cspConfig->styleNonceTag = '{custom-style-tag}'; + + $response = new Response($appConfig); + $response->pretend(true); + + // Inject the custom CSP config + $reflection = new \ReflectionClass($response); + $cspProperty = $reflection->getProperty('CSP'); + $cspProperty->setValue($response, new ContentSecurityPolicy($cspConfig)); + + $body = ''; + $response->setBody($body); + + ob_start(); + $response->send(); + $actual = ob_get_contents(); + ob_end_clean(); + + // Custom nonce placeholders should be removed when CSP is disabled + $this->assertStringNotContainsString('{custom-script-tag}', $actual); + $this->assertStringNotContainsString('{custom-style-tag}', $actual); + $this->assertStringContainsString('', $actual); + $this->assertStringContainsString('', $actual); + } + + public function testSendWithCSPDisabledDoesNotAffectBodyWithoutNonceTags(): void + { + $config = new App(); + $config->CSPEnabled = false; + + $response = new Response($config); + $response->pretend(true); + + $body = ''; + $response->setBody($body); + + ob_start(); + $response->send(); + $actual = ob_get_contents(); + ob_end_clean(); + + // Body without nonce tags should remain unchanged + $this->assertSame($body, $actual); + } } From 466930af19e6ee21a2bc8bf6c19468dfd3df14f8 Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Sun, 8 Feb 2026 18:17:56 +0530 Subject: [PATCH 03/11] cs-fix --- tests/system/HTTP/ContentSecurityPolicyTest.php | 10 +++++----- tests/system/HTTP/ResponseTest.php | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/system/HTTP/ContentSecurityPolicyTest.php b/tests/system/HTTP/ContentSecurityPolicyTest.php index 6e3c320c1eaf..845fc4f60338 100644 --- a/tests/system/HTTP/ContentSecurityPolicyTest.php +++ b/tests/system/HTTP/ContentSecurityPolicyTest.php @@ -943,7 +943,7 @@ public function testClearNoncePlaceholdersWithDefaultTags(): void $config = new CSPConfig(); $csp = new ContentSecurityPolicy($config); - $body = 'Test {csp-script-nonce} and {csp-style-nonce} here'; + $body = 'Test {csp-script-nonce} and {csp-style-nonce} here'; $cleaned = $csp->clearNoncePlaceholders($body); $this->assertSame('Test and here', $cleaned); @@ -958,7 +958,7 @@ public function testClearNoncePlaceholdersWithCustomTags(): void $config->styleNonceTag = '{custom-style-nonce}'; $csp = new ContentSecurityPolicy($config); - $body = 'Test {custom-script-nonce} and {custom-style-nonce} here'; + $body = 'Test {custom-script-nonce} and {custom-style-nonce} here'; $cleaned = $csp->clearNoncePlaceholders($body); $this->assertSame('Test and here', $cleaned); @@ -971,7 +971,7 @@ public function testClearNoncePlaceholdersWithEmptyBody(): void $config = new CSPConfig(); $csp = new ContentSecurityPolicy($config); - $body = ''; + $body = ''; $cleaned = $csp->clearNoncePlaceholders($body); $this->assertSame('', $cleaned); @@ -982,7 +982,7 @@ public function testClearNoncePlaceholdersWithNoPlaceholders(): void $config = new CSPConfig(); $csp = new ContentSecurityPolicy($config); - $body = 'Test body with no placeholders'; + $body = 'Test body with no placeholders'; $cleaned = $csp->clearNoncePlaceholders($body); $this->assertSame($body, $cleaned); @@ -993,7 +993,7 @@ public function testClearNoncePlaceholdersWithMultiplePlaceholders(): void $config = new CSPConfig(); $csp = new ContentSecurityPolicy($config); - $body = ''; + $body = ''; $cleaned = $csp->clearNoncePlaceholders($body); $this->assertStringNotContainsString('{csp-script-nonce}', $cleaned); diff --git a/tests/system/HTTP/ResponseTest.php b/tests/system/HTTP/ResponseTest.php index 9ee3612da492..63e87e2013ac 100644 --- a/tests/system/HTTP/ResponseTest.php +++ b/tests/system/HTTP/ResponseTest.php @@ -24,6 +24,7 @@ use DateTimeZone; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; +use ReflectionClass; /** * @internal @@ -615,7 +616,7 @@ public function testSendRemovesCustomNoncePlaceholdersWhenCSPDisabled(): void $response->pretend(true); // Inject the custom CSP config - $reflection = new \ReflectionClass($response); + $reflection = new ReflectionClass($response); $cspProperty = $reflection->getProperty('CSP'); $cspProperty->setValue($response, new ContentSecurityPolicy($cspConfig)); From ca867f777c7f224dbc1033b0e9ca853469a65036 Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Sun, 8 Feb 2026 18:26:23 +0530 Subject: [PATCH 04/11] Fix static analysis errors --- tests/system/HTTP/ResponseTest.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/system/HTTP/ResponseTest.php b/tests/system/HTTP/ResponseTest.php index 63e87e2013ac..a9ab2c8dcae9 100644 --- a/tests/system/HTTP/ResponseTest.php +++ b/tests/system/HTTP/ResponseTest.php @@ -596,10 +596,10 @@ public function testSendRemovesDefaultNoncePlaceholdersWhenCSPDisabled(): void ob_end_clean(); // Nonce placeholders should be removed when CSP is disabled - $this->assertStringNotContainsString('{csp-script-nonce}', $actual); - $this->assertStringNotContainsString('{csp-style-nonce}', $actual); - $this->assertStringContainsString('', $actual); - $this->assertStringContainsString('', $actual); + $this->assertStringNotContainsString('{csp-script-nonce}', (string) $actual); + $this->assertStringNotContainsString('{csp-style-nonce}', (string) $actual); + $this->assertStringContainsString('', (string) $actual); + $this->assertStringContainsString('', (string) $actual); } public function testSendRemovesCustomNoncePlaceholdersWhenCSPDisabled(): void @@ -629,10 +629,10 @@ public function testSendRemovesCustomNoncePlaceholdersWhenCSPDisabled(): void ob_end_clean(); // Custom nonce placeholders should be removed when CSP is disabled - $this->assertStringNotContainsString('{custom-script-tag}', $actual); - $this->assertStringNotContainsString('{custom-style-tag}', $actual); - $this->assertStringContainsString('', $actual); - $this->assertStringContainsString('', $actual); + $this->assertStringNotContainsString('{custom-script-tag}', (string) $actual); + $this->assertStringNotContainsString('{custom-style-tag}', (string) $actual); + $this->assertStringContainsString('', (string) $actual); + $this->assertStringContainsString('', (string) $actual); } public function testSendWithCSPDisabledDoesNotAffectBodyWithoutNonceTags(): void From e42537f6803c279fd8c612c637196cb1b9e4814c Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Sun, 8 Feb 2026 18:36:45 +0530 Subject: [PATCH 05/11] Fixed `Argument #1 ($text) must be of type string, null given` error --- system/HTTP/ResponseTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/HTTP/ResponseTrait.php b/system/HTTP/ResponseTrait.php index 74b9e10c530d..3abea3592efd 100644 --- a/system/HTTP/ResponseTrait.php +++ b/system/HTTP/ResponseTrait.php @@ -370,7 +370,7 @@ public function send() if ($this->CSP->enabled()) { $this->CSP->finalize($this); } else { - $this->body = $this->CSP->clearNoncePlaceholders($this->body); + $this->body = $this->CSP->clearNoncePlaceholders($this->body ?? ''); } $this->sendHeaders(); From 65b1a0472d32fe152249892101b1367f9a175a4b Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Tue, 10 Feb 2026 10:13:42 +0530 Subject: [PATCH 06/11] Apply code suggestions Co-authored-by: John Paul E. Balandan, CPA --- system/HTTP/ContentSecurityPolicy.php | 8 +++ system/HTTP/ResponseTrait.php | 6 +- .../system/HTTP/ContentSecurityPolicyTest.php | 63 ------------------- tests/system/HTTP/ResponseTest.php | 52 +++++++++++++-- 4 files changed, 57 insertions(+), 72 deletions(-) diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index 203e867372da..394fb473711b 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -902,6 +902,10 @@ protected function generateNonces(ResponseInterface $response) $pattern = sprintf('/(%s|%s)/', preg_quote($this->styleNonceTag, '/'), preg_quote($this->scriptNonceTag, '/')); $body = preg_replace_callback($pattern, function ($match): string { + if (! $this->enabled()) { + return ''; + } + $nonce = $match[0] === $this->styleNonceTag ? $this->getStyleNonce() : $this->getScriptNonce(); return "nonce=\"{$nonce}\""; @@ -923,6 +927,10 @@ protected function buildHeaders(ResponseInterface $response) $response->setHeader('Content-Security-Policy-Report-Only', []); $response->setHeader('Reporting-Endpoints', []); + if (! $this->enabled()) { + return; + } + if (in_array($this->baseURI, ['', null, []], true)) { $this->baseURI = 'self'; } diff --git a/system/HTTP/ResponseTrait.php b/system/HTTP/ResponseTrait.php index 3abea3592efd..3063e44116fb 100644 --- a/system/HTTP/ResponseTrait.php +++ b/system/HTTP/ResponseTrait.php @@ -367,11 +367,7 @@ public function send() { // If we're enforcing a Content Security Policy, // we need to give it a chance to build out it's headers. - if ($this->CSP->enabled()) { - $this->CSP->finalize($this); - } else { - $this->body = $this->CSP->clearNoncePlaceholders($this->body ?? ''); - } + $this->CSP->finalize($this); $this->sendHeaders(); $this->sendCookies(); diff --git a/tests/system/HTTP/ContentSecurityPolicyTest.php b/tests/system/HTTP/ContentSecurityPolicyTest.php index 845fc4f60338..af9638b6a8ba 100644 --- a/tests/system/HTTP/ContentSecurityPolicyTest.php +++ b/tests/system/HTTP/ContentSecurityPolicyTest.php @@ -937,67 +937,4 @@ public function testClearDirective(): void $this->assertNotContains('report-uri http://example.com/csp/reports', $directives); $this->assertNotContains('report-to default', $directives); } - - public function testClearNoncePlaceholdersWithDefaultTags(): void - { - $config = new CSPConfig(); - $csp = new ContentSecurityPolicy($config); - - $body = 'Test {csp-script-nonce} and {csp-style-nonce} here'; - $cleaned = $csp->clearNoncePlaceholders($body); - - $this->assertSame('Test and here', $cleaned); - $this->assertStringNotContainsString('{csp-script-nonce}', $cleaned); - $this->assertStringNotContainsString('{csp-style-nonce}', $cleaned); - } - - public function testClearNoncePlaceholdersWithCustomTags(): void - { - $config = new CSPConfig(); - $config->scriptNonceTag = '{custom-script-nonce}'; - $config->styleNonceTag = '{custom-style-nonce}'; - $csp = new ContentSecurityPolicy($config); - - $body = 'Test {custom-script-nonce} and {custom-style-nonce} here'; - $cleaned = $csp->clearNoncePlaceholders($body); - - $this->assertSame('Test and here', $cleaned); - $this->assertStringNotContainsString('{custom-script-nonce}', $cleaned); - $this->assertStringNotContainsString('{custom-style-nonce}', $cleaned); - } - - public function testClearNoncePlaceholdersWithEmptyBody(): void - { - $config = new CSPConfig(); - $csp = new ContentSecurityPolicy($config); - - $body = ''; - $cleaned = $csp->clearNoncePlaceholders($body); - - $this->assertSame('', $cleaned); - } - - public function testClearNoncePlaceholdersWithNoPlaceholders(): void - { - $config = new CSPConfig(); - $csp = new ContentSecurityPolicy($config); - - $body = 'Test body with no placeholders'; - $cleaned = $csp->clearNoncePlaceholders($body); - - $this->assertSame($body, $cleaned); - } - - public function testClearNoncePlaceholdersWithMultiplePlaceholders(): void - { - $config = new CSPConfig(); - $csp = new ContentSecurityPolicy($config); - - $body = ''; - $cleaned = $csp->clearNoncePlaceholders($body); - - $this->assertStringNotContainsString('{csp-script-nonce}', $cleaned); - $this->assertStringNotContainsString('{csp-style-nonce}', $cleaned); - $this->assertSame('', $cleaned); - } } diff --git a/tests/system/HTTP/ResponseTest.php b/tests/system/HTTP/ResponseTest.php index a9ab2c8dcae9..cd193b202da5 100644 --- a/tests/system/HTTP/ResponseTest.php +++ b/tests/system/HTTP/ResponseTest.php @@ -635,7 +635,7 @@ public function testSendRemovesCustomNoncePlaceholdersWhenCSPDisabled(): void $this->assertStringContainsString('', (string) $actual); } - public function testSendWithCSPDisabledDoesNotAffectBodyWithoutNonceTags(): void + public function testSendNoEffectWhenBodyEmptyAndCSPDisabled(): void { $config = new App(); $config->CSPEnabled = false; @@ -643,7 +643,7 @@ public function testSendWithCSPDisabledDoesNotAffectBodyWithoutNonceTags(): void $response = new Response($config); $response->pretend(true); - $body = ''; + $body = ''; $response->setBody($body); ob_start(); @@ -651,7 +651,51 @@ public function testSendWithCSPDisabledDoesNotAffectBodyWithoutNonceTags(): void $actual = ob_get_contents(); ob_end_clean(); - // Body without nonce tags should remain unchanged - $this->assertSame($body, $actual); + $this->assertSame('', (string) $actual); + } + + public function testSendNoEffectWithNoPlaceholdersAndCSPDisabled(): void + { + $config = new App(); + $config->CSPEnabled = false; + + $response = new Response($config); + $response->pretend(true); + + $body = 'Test

No placeholders here

'; + $response->setBody($body); + + ob_start(); + $response->send(); + $actual = ob_get_contents(); + ob_end_clean(); + + // Body should be unchanged when there are no placeholders and CSP is disabled + $this->assertSame($body, (string) $actual); + } + + public function testSendRemovesMultiplePlaceholdersWhenCSPDisabled(): void + { + $config = new App(); + $config->CSPEnabled = false; + + $response = new Response($config); + $response->pretend(true); + + $body = ''; + $response->setBody($body); + + ob_start(); + $response->send(); + $actual = ob_get_contents(); + ob_end_clean(); + + // All nonce placeholders should be removed when CSP is disabled + $this->assertStringNotContainsString('{csp-script-nonce}', (string) $actual); + $this->assertStringNotContainsString('{csp-style-nonce}', (string) $actual); + $this->assertStringContainsString('', (string) $actual); + $this->assertStringContainsString('', (string) $actual); + $this->assertStringContainsString('', (string) $actual); + $this->assertStringContainsString('', (string) $actual); } } From ea893a9414119cb887f8a393da6733bd4f1fe4a3 Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Tue, 10 Feb 2026 11:00:42 +0530 Subject: [PATCH 07/11] Fix the failing test case --- system/HTTP/ContentSecurityPolicy.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index 394fb473711b..ac27c9958950 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -923,14 +923,14 @@ protected function generateNonces(ResponseInterface $response) */ protected function buildHeaders(ResponseInterface $response) { - $response->setHeader('Content-Security-Policy', []); - $response->setHeader('Content-Security-Policy-Report-Only', []); - $response->setHeader('Reporting-Endpoints', []); - if (! $this->enabled()) { return; } + $response->setHeader('Content-Security-Policy', []); + $response->setHeader('Content-Security-Policy-Report-Only', []); + $response->setHeader('Reporting-Endpoints', []); + if (in_array($this->baseURI, ['', null, []], true)) { $this->baseURI = 'self'; } From 030f4a00155e5045fe7fb1c20a6f3f7d7ca9f7f1 Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Wed, 11 Feb 2026 14:48:37 +0530 Subject: [PATCH 08/11] Remove unused function --- system/HTTP/ContentSecurityPolicy.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index 9685d2c92feb..dc3f2abfa4c1 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -1064,9 +1064,4 @@ public function clearDirective(string $directive): void $this->{$this->directives[$directive]} = []; } - - public function clearNoncePlaceholders(string $text): string - { - return str_replace([$this->styleNonceTag, $this->scriptNonceTag], '', $text); - } } From db3d0bddf8c90993952be9eb7c89199fa5591cd3 Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Wed, 11 Feb 2026 15:18:12 +0530 Subject: [PATCH 09/11] Updated docs --- user_guide_src/source/changelogs/v4.7.1.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/user_guide_src/source/changelogs/v4.7.1.rst b/user_guide_src/source/changelogs/v4.7.1.rst index 8a94a731a413..c3b872c8611b 100644 --- a/user_guide_src/source/changelogs/v4.7.1.rst +++ b/user_guide_src/source/changelogs/v4.7.1.rst @@ -32,6 +32,7 @@ Deprecations Bugs Fixed ********** +- **ContentSecurityPolicy:** Fixed a bug where custom CSP tags were not removed from generated HTML when CSP was disabled. The method now ensures that all custom CSP tags are removed from the generated HTML. - **ContentSecurityPolicy:** Fixed a bug where ``generateNonces()`` produces corrupted JSON responses by replacing CSP nonce placeholders with unescaped double quotes. The method now automatically JSON-escapes nonce attributes when the response Content-Type is JSON. - **Session:** Fixed a bug in ``MemcachedHandler`` where the constructor incorrectly threw an exception when ``savePath`` was not empty. From 10c90dcde0cafa94ca45ad99828bada157625658 Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Thu, 12 Feb 2026 14:43:42 +0530 Subject: [PATCH 10/11] Addressing the review on tests. --- tests/system/HTTP/ResponseTest.php | 52 +++++++++++++++--------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/tests/system/HTTP/ResponseTest.php b/tests/system/HTTP/ResponseTest.php index cd193b202da5..6c4a8f53edf2 100644 --- a/tests/system/HTTP/ResponseTest.php +++ b/tests/system/HTTP/ResponseTest.php @@ -592,14 +592,14 @@ public function testSendRemovesDefaultNoncePlaceholdersWhenCSPDisabled(): void ob_start(); $response->send(); - $actual = ob_get_contents(); - ob_end_clean(); + $actual = ob_get_clean(); // Nonce placeholders should be removed when CSP is disabled - $this->assertStringNotContainsString('{csp-script-nonce}', (string) $actual); - $this->assertStringNotContainsString('{csp-style-nonce}', (string) $actual); - $this->assertStringContainsString('', (string) $actual); - $this->assertStringContainsString('', (string) $actual); + $this->assertIsString($actual); + $this->assertStringNotContainsString('{csp-script-nonce}', $actual); + $this->assertStringNotContainsString('{csp-style-nonce}', $actual); + $this->assertStringContainsString('', $actual); + $this->assertStringContainsString('', $actual); } public function testSendRemovesCustomNoncePlaceholdersWhenCSPDisabled(): void @@ -625,14 +625,14 @@ public function testSendRemovesCustomNoncePlaceholdersWhenCSPDisabled(): void ob_start(); $response->send(); - $actual = ob_get_contents(); - ob_end_clean(); + $actual = ob_get_clean(); // Custom nonce placeholders should be removed when CSP is disabled - $this->assertStringNotContainsString('{custom-script-tag}', (string) $actual); - $this->assertStringNotContainsString('{custom-style-tag}', (string) $actual); - $this->assertStringContainsString('', (string) $actual); - $this->assertStringContainsString('', (string) $actual); + $this->assertIsString($actual); + $this->assertStringNotContainsString('{custom-script-tag}', $actual); + $this->assertStringNotContainsString('{custom-style-tag}', $actual); + $this->assertStringContainsString('', $actual); + $this->assertStringContainsString('', $actual); } public function testSendNoEffectWhenBodyEmptyAndCSPDisabled(): void @@ -648,10 +648,10 @@ public function testSendNoEffectWhenBodyEmptyAndCSPDisabled(): void ob_start(); $response->send(); - $actual = ob_get_contents(); - ob_end_clean(); + $actual = ob_get_clean(); - $this->assertSame('', (string) $actual); + $this->assertIsString($actual); + $this->assertSame('', $actual); } public function testSendNoEffectWithNoPlaceholdersAndCSPDisabled(): void @@ -667,11 +667,11 @@ public function testSendNoEffectWithNoPlaceholdersAndCSPDisabled(): void ob_start(); $response->send(); - $actual = ob_get_contents(); - ob_end_clean(); + $actual = ob_get_clean(); // Body should be unchanged when there are no placeholders and CSP is disabled - $this->assertSame($body, (string) $actual); + $this->assertIsString($actual); + $this->assertSame($body, $actual); } public function testSendRemovesMultiplePlaceholdersWhenCSPDisabled(): void @@ -687,15 +687,15 @@ public function testSendRemovesMultiplePlaceholdersWhenCSPDisabled(): void ob_start(); $response->send(); - $actual = ob_get_contents(); - ob_end_clean(); + $actual = ob_get_clean(); // All nonce placeholders should be removed when CSP is disabled - $this->assertStringNotContainsString('{csp-script-nonce}', (string) $actual); - $this->assertStringNotContainsString('{csp-style-nonce}', (string) $actual); - $this->assertStringContainsString('', (string) $actual); - $this->assertStringContainsString('', (string) $actual); - $this->assertStringContainsString('', (string) $actual); - $this->assertStringContainsString('', (string) $actual); + $this->assertIsString($actual); + $this->assertStringNotContainsString('{csp-script-nonce}', $actual); + $this->assertStringNotContainsString('{csp-style-nonce}', $actual); + $this->assertStringContainsString('', $actual); + $this->assertStringContainsString('', $actual); + $this->assertStringContainsString('', $actual); + $this->assertStringContainsString('', $actual); } } From 68084298965b70f5a95a908f980dc341fc3e50b8 Mon Sep 17 00:00:00 2001 From: patel-vansh Date: Thu, 12 Feb 2026 14:54:24 +0530 Subject: [PATCH 11/11] Addressing the case when both $CSPEnabled and $autoNonce are false. --- system/HTTP/ContentSecurityPolicy.php | 8 +++++++ tests/system/HTTP/ResponseTest.php | 32 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index dc3f2abfa4c1..8a4af8f77363 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -429,6 +429,14 @@ public function finalize(ResponseInterface $response) { if ($this->autoNonce) { $this->generateNonces($response); + } else { + // If we're not auto-generating nonces, we should remove any nonce placeholders from the body to prevent them from being rendered. + $body = (string) $response->getBody(); + + if ($body !== '') { + $body = str_replace([$this->styleNonceTag, $this->scriptNonceTag], '', $body); + $response->setBody($body); + } } $this->buildHeaders($response); diff --git a/tests/system/HTTP/ResponseTest.php b/tests/system/HTTP/ResponseTest.php index 6c4a8f53edf2..048da85beea9 100644 --- a/tests/system/HTTP/ResponseTest.php +++ b/tests/system/HTTP/ResponseTest.php @@ -698,4 +698,36 @@ public function testSendRemovesMultiplePlaceholdersWhenCSPDisabled(): void $this->assertStringContainsString('', $actual); $this->assertStringContainsString('', $actual); } + + public function testSendRemovesPlaceholdersWhenBothCSPAndAutoNonceAreDisabled(): void + { + $appConfig = new App(); + $appConfig->CSPEnabled = false; + + // Create custom CSP config with custom nonce tags + $cspConfig = new \Config\ContentSecurityPolicy(); + $cspConfig->autoNonce = false; + + $response = new Response($appConfig); + $response->pretend(true); + + // Inject the custom CSP config + $reflection = new ReflectionClass($response); + $cspProperty = $reflection->getProperty('CSP'); + $cspProperty->setValue($response, new ContentSecurityPolicy($cspConfig)); + + $body = ''; + $response->setBody($body); + + ob_start(); + $response->send(); + $actual = ob_get_clean(); + + // Custom nonce placeholders should be removed when CSP is disabled + $this->assertIsString($actual); + $this->assertStringNotContainsString('{csp-script-nonce}', $actual); + $this->assertStringNotContainsString('{csp-style-nonce}', $actual); + $this->assertStringContainsString('', $actual); + $this->assertStringContainsString('', $actual); + } }