From a8d95ceb87651e17ade08efee7a3b8b35001603b Mon Sep 17 00:00:00 2001 From: Greg Roach Date: Fri, 24 Apr 2026 13:02:45 +0100 Subject: Add CSP to SVG error responses --- app/Factories/ImageFactory.php | 10 +++++----- tests/app/Factories/ImageFactoryTest.php | 13 +++++++++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/app/Factories/ImageFactory.php b/app/Factories/ImageFactory.php index 0e39d7993b..7141bb1497 100644 --- a/app/Factories/ImageFactory.php +++ b/app/Factories/ImageFactory.php @@ -260,9 +260,9 @@ class ImageFactory implements ImageFactoryInterface $svg = view(name: 'errors/image-svg', data: ['status' => $text]); // We can't send the actual status code, as browsers won't show images with 4xx/5xx. - return response(content: $svg, code: StatusCodeInterface::STATUS_OK, headers: [ - 'content-type' => 'image/svg+xml', - ]); + return response(content: $svg) + ->withHeader('content-type', 'image/svg+xml') + ->withHeader('content-security-policy', 'default-src none'); } protected function imageResponse(string $data, string $mime_type, string $filename): ResponseInterface @@ -272,10 +272,10 @@ class ImageFactory implements ImageFactoryInterface ->withHeader('x-image-exception', 'SVG image blocked due to XSS.'); } - // HTML files may contain javascript and iframes, so use content-security-policy to disable them. + // HTML files may contain JavaScript and iframes, so use content-security-policy to disable them. $response = response($data) ->withHeader('content-type', $mime_type) - ->withHeader('content-security-policy', 'script-src none;frame-src none'); + ->withHeader('content-security-policy', 'default-src none'); if ($filename === '') { return $response; diff --git a/tests/app/Factories/ImageFactoryTest.php b/tests/app/Factories/ImageFactoryTest.php index b14f9ee8d3..3f77d78c1c 100644 --- a/tests/app/Factories/ImageFactoryTest.php +++ b/tests/app/Factories/ImageFactoryTest.php @@ -19,14 +19,23 @@ declare(strict_types=1); namespace Fisharebest\Webtrees\Factories; +use Fisharebest\Webtrees\Services\PhpService; use Fisharebest\Webtrees\TestCase; use PHPUnit\Framework\Attributes\CoversClass; #[CoversClass(ImageFactory::class)] class ImageFactoryTest extends TestCase { - public function testClass(): void + public function testReplacementImageResponseSetsContentSecurityPolicyHeader(): void { - self::assertTrue(class_exists(ImageFactory::class)); + $php_service = $this->createStub(PhpService::class); + $image_factory = new ImageFactory($php_service); + $response = $image_factory->replacementImageResponse('404'); + + self::assertSame('image/svg+xml', $response->getHeaderLine('content-type')); + self::assertSame( + 'default-src none', + $response->getHeaderLine('content-security-policy'), + ); } } -- cgit v1.3