From 29a293612426dd1186af34472924d7885d9a11f4 Mon Sep 17 00:00:00 2001 From: lazerg Date: Thu, 24 Sep 2026 06:31:39 +0500 Subject: [PATCH 1/3] Validate Glide signatures when the site URL is on another domain --- src/Http/Controllers/GlideController.php | 7 ++++- tests/Imaging/GlideTest.php | 33 ++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/Http/Controllers/GlideController.php b/src/Http/Controllers/GlideController.php index 655fd4fa2fb..0c48f03840a 100644 --- a/src/Http/Controllers/GlideController.php +++ b/src/Http/Controllers/GlideController.php @@ -218,7 +218,12 @@ private function validateSignature() return; } - $path = Str::after($this->request->url(), Site::current()->absoluteUrl()); + $url = $this->request->url(); + $siteUrl = Site::current()->absoluteUrl(); + + $path = Str::startsWith($url, $siteUrl) + ? Str::after($url, $siteUrl) + : $this->request->getPathInfo(); try { SignatureFactory::create(Config::getAppKey())->validateRequest($path, $this->request->query->all()); diff --git a/tests/Imaging/GlideTest.php b/tests/Imaging/GlideTest.php index 4848b8816dc..149373816bd 100644 --- a/tests/Imaging/GlideTest.php +++ b/tests/Imaging/GlideTest.php @@ -397,6 +397,39 @@ public function hybrid_caching_only_registers_the_cache_path_route() $this->get($signedUrl)->assertNotFound(); } + #[Test] + #[DataProvider('signedUrlSiteProvider')] + public function it_validates_the_signature_against_the_site_url($siteUrl) + { + $this->setSites([ + 'en' => ['url' => $siteUrl, 'locale' => 'en_US'], + ]); + + $url = $this->app->make(UrlBuilder::class)->build($this->createAsset(), ['w' => 100]); + + $this->get($url)->assertOk()->streamedContent(); + } + + public static function signedUrlSiteProvider() + { + return [ + 'same domain' => ['http://localhost/'], + 'different domain' => ['https://www.example.com/'], + ]; + } + + #[Test] + public function it_rejects_an_invalid_signature_when_the_site_url_is_on_a_different_domain() + { + $this->setSites([ + 'en' => ['url' => 'https://www.example.com/', 'locale' => 'en_US'], + ]); + + $url = $this->app->make(UrlBuilder::class)->build($this->createAsset(), ['w' => 100]); + + $this->get(str_replace('w=100', 'w=200', $url))->assertStatus(400); + } + #[Test] public function cache_true_without_cache_path_will_throw_exception() { From 1f362dd76de2eb0e0a8276b24776ec032dff94a0 Mon Sep 17 00:00:00 2001 From: lazerg Date: Thu, 24 Sep 2026 06:34:47 +0500 Subject: [PATCH 2/3] Don't treat a site URL as a prefix of a different host --- src/Http/Controllers/GlideController.php | 2 +- tests/Imaging/GlideTest.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Http/Controllers/GlideController.php b/src/Http/Controllers/GlideController.php index 0c48f03840a..2081c6648a7 100644 --- a/src/Http/Controllers/GlideController.php +++ b/src/Http/Controllers/GlideController.php @@ -221,7 +221,7 @@ private function validateSignature() $url = $this->request->url(); $siteUrl = Site::current()->absoluteUrl(); - $path = Str::startsWith($url, $siteUrl) + $path = Str::startsWith(Str::ensureRight($url, '/'), Str::ensureRight($siteUrl, '/')) ? Str::after($url, $siteUrl) : $this->request->getPathInfo(); diff --git a/tests/Imaging/GlideTest.php b/tests/Imaging/GlideTest.php index 149373816bd..a3be93db4d7 100644 --- a/tests/Imaging/GlideTest.php +++ b/tests/Imaging/GlideTest.php @@ -415,6 +415,7 @@ public static function signedUrlSiteProvider() return [ 'same domain' => ['http://localhost/'], 'different domain' => ['https://www.example.com/'], + 'domain starting with the app domain' => ['http://local/'], ]; } From d5e3370bd979160ca76ebbd415b25991cf2bc716 Mon Sep 17 00:00:00 2001 From: lazerg Date: Fri, 6 Nov 2026 15:46:15 +0500 Subject: [PATCH 3/3] Strip the site path prefix when validating Glide signatures --- src/Http/Controllers/GlideController.php | 17 ++++++++++++++++- tests/Imaging/GlideTest.php | 21 +++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/Http/Controllers/GlideController.php b/src/Http/Controllers/GlideController.php index 2081c6648a7..0e9fbcf9da4 100644 --- a/src/Http/Controllers/GlideController.php +++ b/src/Http/Controllers/GlideController.php @@ -16,6 +16,7 @@ use Statamic\Facades\Config; use Statamic\Facades\Glide; use Statamic\Facades\Site; +use Statamic\Facades\URL; use Statamic\Imaging\ImageGenerator; use Statamic\Support\Str; @@ -223,7 +224,7 @@ private function validateSignature() $path = Str::startsWith(Str::ensureRight($url, '/'), Str::ensureRight($siteUrl, '/')) ? Str::after($url, $siteUrl) - : $this->request->getPathInfo(); + : $this->removeSitePrefix($this->request->getPathInfo()); try { SignatureFactory::create(Config::getAppKey())->validateRequest($path, $this->request->query->all()); @@ -231,4 +232,18 @@ private function validateSignature() abort(400, $e->getMessage()); } } + + /** + * Remove the longest site path prefix (e.g. /fr) from the path. + */ + private function removeSitePrefix(string $path): string + { + $prefix = Site::all() + ->map(fn ($site) => Str::removeRight(URL::makeRelative($site->url()), '/')) + ->filter(fn ($prefix) => $prefix !== '' && Str::startsWith($path, $prefix.'/')) + ->sortByDesc(fn ($prefix) => strlen($prefix)) + ->first(); + + return $prefix ? Str::after($path, $prefix) : $path; + } } diff --git a/tests/Imaging/GlideTest.php b/tests/Imaging/GlideTest.php index a3be93db4d7..018ad1d69ba 100644 --- a/tests/Imaging/GlideTest.php +++ b/tests/Imaging/GlideTest.php @@ -419,6 +419,19 @@ public static function signedUrlSiteProvider() ]; } + #[Test] + #[DefineEnvironment('pathPrefixedSitesOnAnotherDomain')] + public function it_validates_the_signature_for_a_path_prefixed_site_on_a_different_domain() + { + Site::setCurrent('fr'); + + $url = $this->app->make(UrlBuilder::class)->build($this->createAsset(), ['w' => 100]); + + $this->assertStringStartsWith('/fr/img/', $url); + + $this->get($url)->assertOk()->streamedContent(); + } + #[Test] public function it_rejects_an_invalid_signature_when_the_site_url_is_on_a_different_domain() { @@ -653,6 +666,14 @@ private function fakeFfmpeg() }); } + protected function pathPrefixedSitesOnAnotherDomain($app) + { + $app->booting(fn () => $this->setSites([ + 'en' => ['url' => 'https://www.example.com/', 'locale' => 'en_US'], + 'fr' => ['url' => 'https://www.example.com/fr/', 'locale' => 'fr_FR'], + ])); + } + protected function hybridCaching($app) { $app['config']->set('statamic.assets.image_manipulation.cache', 'hybrid');