From 67da259bd371b3613f6b6eeb7ce3c405fb5cc41d Mon Sep 17 00:00:00 2001 From: Bogdan Date: Tue, 13 Oct 2026 20:55:25 +0200 Subject: [PATCH] fix: fix array handling in ContentSecurityPolicy::setDefaultSrc() and add addDefaultSrc() --- system/HTTP/ContentSecurityPolicy.php | 20 ++++++++- .../system/HTTP/ContentSecurityPolicyTest.php | 44 +++++++++++++++++++ user_guide_src/source/changelogs/v4.7.6.rst | 3 ++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index dfea52a62772..faaa526acc94 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -521,12 +521,30 @@ public function addConnectSrc($uri, ?bool $explicitReporting = null) * @see http://www.w3.org/TR/CSP/#directive-default-src * * @param list|string $uri + */ + public function addDefaultSrc(array|string $uri, ?bool $explicitReporting = null): static + { + $this->addOption($uri, 'defaultSrc', $explicitReporting ?? $this->reportOnly); + + return $this; + } + + /** + * Sets the `default-src` directive, replacing any existing sources. + * + * `default-src` is the URI that is used for many of the settings when + * no other source has been set. + * + * @see http://www.w3.org/TR/CSP/#directive-default-src + * + * @param list|string $uri * * @return $this */ public function setDefaultSrc($uri, ?bool $explicitReporting = null) { - $this->defaultSrc = [(string) $uri => $explicitReporting ?? $this->reportOnly]; + $this->defaultSrc = []; + $this->addDefaultSrc($uri, $explicitReporting); return $this; } diff --git a/tests/system/HTTP/ContentSecurityPolicyTest.php b/tests/system/HTTP/ContentSecurityPolicyTest.php index 780cb99f31ea..a85cd107e7c3 100644 --- a/tests/system/HTTP/ContentSecurityPolicyTest.php +++ b/tests/system/HTTP/ContentSecurityPolicyTest.php @@ -617,6 +617,50 @@ public function testDefaultSrc(): void $this->assertContains('default-src iffy.com', $this->getCspDirectives($header)); } + #[PreserveGlobalState(false)] + #[RunInSeparateProcess] + public function testDefaultSrcRich(): void + { + $this->csp->reportOnly(false); + $this->csp->setDefaultSrc(['self', 'maybe.com', 'iffy.com']); + $this->assertTrue($this->work()); + + $header = $this->getHeaderEmitted('Content-Security-Policy'); + $this->assertIsString($header); + $this->assertContains("default-src 'self' maybe.com iffy.com", $this->getCspDirectives($header)); + } + + #[PreserveGlobalState(false)] + #[RunInSeparateProcess] + public function testAddDefaultSrc(): void + { + $this->csp->addDefaultSrc('maybe.com'); + $this->csp->addDefaultSrc('iffy.com', true); + $this->assertTrue($this->work()); + + $header = $this->getHeaderEmitted('Content-Security-Policy-Report-Only'); + $this->assertIsString($header); + $this->assertContains('default-src iffy.com', $this->getCspDirectives($header)); + + $header = $this->getHeaderEmitted('Content-Security-Policy'); + $this->assertIsString($header); + $this->assertContains('default-src maybe.com', $this->getCspDirectives($header)); + } + + #[PreserveGlobalState(false)] + #[RunInSeparateProcess] + public function testAddDefaultSrcAppends(): void + { + $this->csp->reportOnly(false); + $this->csp->setDefaultSrc('maybe.com'); + $this->csp->addDefaultSrc('iffy.com'); + $this->assertTrue($this->work()); + + $header = $this->getHeaderEmitted('Content-Security-Policy'); + $this->assertIsString($header); + $this->assertContains('default-src maybe.com iffy.com', $this->getCspDirectives($header)); + } + #[PreserveGlobalState(false)] #[RunInSeparateProcess] public function testReportURI(): void diff --git a/user_guide_src/source/changelogs/v4.7.6.rst b/user_guide_src/source/changelogs/v4.7.6.rst index 75e6f890047c..843eff4722bd 100644 --- a/user_guide_src/source/changelogs/v4.7.6.rst +++ b/user_guide_src/source/changelogs/v4.7.6.rst @@ -24,6 +24,8 @@ Message Changes Changes ******* +- **HTTP:** Added ``ContentSecurityPolicy::addDefaultSrc()`` to allow appending sources to the ``default-src`` directive. + ************ Deprecations ************ @@ -35,6 +37,7 @@ Bugs Fixed - **Commands:** Fixed a bug where generator commands did not sort the imports of a template with CRLF line endings. - **Format:** Fixed ``XMLFormatter`` so that an array key that becomes empty after sanitization no longer triggers a ``ValueError``; the fallback tag name ``item`` is used instead. Values that cannot be converted to a string, such as objects not implementing ``Stringable``, now throw a ``FormatException`` instead of a fatal ``Error``. - **Helpers:** Fixed a regression in v4.7.5 where ``set_value()`` threw a ``TypeError`` when the default value was not a string or an array, such as an integer, a float, ``null``, or a ``Stringable`` object. Such values are now converted to a string. +- **HTTP:** Fixed a bug in ``ContentSecurityPolicy::setDefaultSrc()`` where passing an array of URIs resulted in an invalid ``'Array'`` directive. - **HTTP:** Fixed stale ``$_REQUEST`` values after ``SiteURIFactory`` normalizes GET parameters, so ``Validation::withRequest()`` reads the normalized values while unrelated REQUEST entries are preserved. - **HTTP:** Fixed a bug where ``IncomingRequest::isSecure()`` did not recognize ``https`` in the ``X-Forwarded-Proto`` header from a trusted proxy when the value contained uppercase letters or surrounding whitespace, or was represented as a single-element array. - **Security:** Fixed a bug where a valid JSON request body that is not an object (for example a JSON array) was corrupted after CSRF verification when the token was sent via the ``X-CSRF-TOKEN`` header.