From 14d3a114eb7f8727a08facd350a90a98a516848e Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 6 Oct 2026 13:10:20 -0400 Subject: [PATCH 1/4] [5.x] 5.74.5 (#15595) --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 785025e9fca..2a998a405e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Release Notes +## 5.74.5 (2026-10-06) + +### What's fixed +- Harden asset authorization [#15196](https://github.com/statamic/cms/issues/15196) by @duncanmcclean +- Harden impersonation action [#15562](https://github.com/statamic/cms/issues/15562) by @jasonvarga +- Tighten SVG sanitization [#15583](https://github.com/statamic/cms/issues/15583) by @jasonvarga +- Harden remote URL validation [#15584](https://github.com/statamic/cms/issues/15584) by @jasonvarga +- Tighten class based validation rules [#15585](https://github.com/statamic/cms/issues/15585) by @jasonvarga +- Harden user file paths [#15593](https://github.com/statamic/cms/issues/15593) by @jasonvarga + + + ## 5.74.4 (2026-08-12) ### What's fixed From 0700a08b1ae671e1d3752f0a836e100ba82c33ed Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Thu, 8 Oct 2026 12:28:43 -0400 Subject: [PATCH 2/4] [5.x] Allow ueberdosis/tiptap-php ^2.1.2 (#15620) Co-authored-by: Claude Opus 5.5 --- composer.json | 2 +- src/Fieldtypes/Bard/LinkMark.php | 4 ++++ src/Fieldtypes/Bard/Marks/Strike.php | 18 +++++++++++++++ src/Providers/BardServiceProvider.php | 2 +- .../GraphQL/Fieldtypes/BardFieldtypeTest.php | 22 +++++++++---------- tests/Fieldtypes/BardTest.php | 15 +++++++++++++ 6 files changed, 50 insertions(+), 13 deletions(-) create mode 100644 src/Fieldtypes/Bard/Marks/Strike.php diff --git a/composer.json b/composer.json index 89ceb8c8f46..cc08cbb39a7 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "symfony/lock": "^6.4", "symfony/var-exporter": "^6.0", "symfony/yaml": "^6.0 || ^7.0", - "ueberdosis/tiptap-php": "^1.4", + "ueberdosis/tiptap-php": "^1.4 || ^2.1.2", "voku/portable-ascii": "^2.0.2", "wilderborn/partyline": "^1.0", "symfony/http-foundation": "^6.4.29 || ^7.3.7", diff --git a/src/Fieldtypes/Bard/LinkMark.php b/src/Fieldtypes/Bard/LinkMark.php index 8bf5cfb7b81..955011d86f8 100644 --- a/src/Fieldtypes/Bard/LinkMark.php +++ b/src/Fieldtypes/Bard/LinkMark.php @@ -17,6 +17,10 @@ public function addOptions() 'rel' => '', 'target' => '_blank', ], + 'allowedProtocols' => [ + 'http', 'https', 'ftp', 'ftps', 'mailto', 'tel', 'callto', 'sms', 'cid', 'xmpp', 'statamic', 'entry', 'asset', + ], + 'isAllowedUri' => fn ($uri) => $this->isAllowedUri($uri), ]; } diff --git a/src/Fieldtypes/Bard/Marks/Strike.php b/src/Fieldtypes/Bard/Marks/Strike.php new file mode 100644 index 00000000000..d46518c6b29 --- /dev/null +++ b/src/Fieldtypes/Bard/Marks/Strike.php @@ -0,0 +1,18 @@ +options['HTMLAttributes'], $HTMLAttributes), + 0, + ]; + } +} diff --git a/src/Providers/BardServiceProvider.php b/src/Providers/BardServiceProvider.php index de5921b4cea..159991d0d1d 100644 --- a/src/Providers/BardServiceProvider.php +++ b/src/Providers/BardServiceProvider.php @@ -37,7 +37,7 @@ public function boot() 'subscript' => new \Tiptap\Marks\Subscript(), 'superscript' => new \Tiptap\Marks\Superscript(), 'small' => new \Statamic\Fieldtypes\Bard\Marks\Small(), - 'strike' => new \Tiptap\Marks\Strike(), + 'strike' => new \Statamic\Fieldtypes\Bard\Marks\Strike(), 'table' => new \Tiptap\Nodes\Table(), 'tableCell' => new \Tiptap\Nodes\TableCell(), 'tableHeader' => new \Tiptap\Nodes\TableHeader(), diff --git a/tests/Feature/GraphQL/Fieldtypes/BardFieldtypeTest.php b/tests/Feature/GraphQL/Fieldtypes/BardFieldtypeTest.php index 1e6f98685c3..ecc864fdb09 100644 --- a/tests/Feature/GraphQL/Fieldtypes/BardFieldtypeTest.php +++ b/tests/Feature/GraphQL/Fieldtypes/BardFieldtypeTest.php @@ -294,17 +294,17 @@ public function it_outputs_nested_bard_fields() EntryFactory::collection('blog')->id('1')->data([ 'title' => 'Main Post', 'things' => [ - ['type' => 'text', 'text' => 'first text'], - ['type' => 'meal', 'food' => 'burger', 'drink' => 'coke', 'extras' => [ - ['type' => 'food', 'item' => 'fries'], - ['type' => 'food', 'item' => 'ketchup'], - ]], - ['type' => 'text', 'text' => 'second text'], - ['type' => 'car', 'make' => 'toyota', 'model' => 'corolla'], - ['type' => 'meal', 'food' => 'salad', 'drink' => 'water', 'extras' => [ - ['type' => 'food', 'item' => 'dressing'], - ]], - ['type' => 'text', 'text' => 'last text'], + ['type' => 'paragraph', 'content' => [['type' => 'text', 'text' => 'first text']]], + ['type' => 'set', 'attrs' => ['id' => '1', 'values' => ['type' => 'meal', 'food' => 'burger', 'drink' => 'coke', 'extras' => [ + ['type' => 'set', 'attrs' => ['id' => '1a', 'values' => ['type' => 'food', 'item' => 'fries']]], + ['type' => 'set', 'attrs' => ['id' => '1b', 'values' => ['type' => 'food', 'item' => 'ketchup']]], + ]]]], + ['type' => 'paragraph', 'content' => [['type' => 'text', 'text' => 'second text']]], + ['type' => 'set', 'attrs' => ['id' => '2', 'values' => ['type' => 'car', 'make' => 'toyota', 'model' => 'corolla']]], + ['type' => 'set', 'attrs' => ['id' => '3', 'values' => ['type' => 'meal', 'food' => 'salad', 'drink' => 'water', 'extras' => [ + ['type' => 'set', 'attrs' => ['id' => '3a', 'values' => ['type' => 'food', 'item' => 'dressing']]], + ]]]], + ['type' => 'paragraph', 'content' => [['type' => 'text', 'text' => 'last text']]], ], ])->create(); diff --git a/tests/Fieldtypes/BardTest.php b/tests/Fieldtypes/BardTest.php index cf43e330fe9..b7eca667cf1 100644 --- a/tests/Fieldtypes/BardTest.php +++ b/tests/Fieldtypes/BardTest.php @@ -186,6 +186,21 @@ public function it_doesnt_augment_when_saved_as_html() $this->assertEquals('

Paragraph

', $this->bard()->augment('

Paragraph

')); } + #[Test] + public function it_augments_strike_marks_to_strike_tags() + { + $data = [ + [ + 'type' => 'paragraph', + 'content' => [ + ['type' => 'text', 'marks' => [['type' => 'strike']], 'text' => 'struck'], + ], + ], + ]; + + $this->assertEquals('

struck

', $this->bard(['sets' => null])->augment($data)); + } + #[Test] public function it_augments_tiptap_v1_snake_case_types_to_v2_camel_case_types() { From 15d9209de92b82d7b57a3faca540a69cee3df203 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Thu, 8 Oct 2026 12:42:27 -0400 Subject: [PATCH 3/4] [5.x] 5.74.6 (#15621) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a998a405e8..6fbed99980d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Release Notes +## 5.74.6 (2026-10-08) + +### What's fixed +- Allow ueberdosis/tiptap-php ^2.1.2 [#15620](https://github.com/statamic/cms/issues/15620) by @jasonvarga + + + ## 5.74.5 (2026-10-06) ### What's fixed From 6c26d8128fe2541d31fa681c5cedc00abb7cc693 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 21 Oct 2026 11:03:49 -0400 Subject: [PATCH 4/4] [5.x] Harden glide query param logic (#15693) --- src/Http/Controllers/GlideController.php | 2 +- tests/Feature/Imaging/GlideControllerTest.php | 80 +++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 tests/Feature/Imaging/GlideControllerTest.php diff --git a/src/Http/Controllers/GlideController.php b/src/Http/Controllers/GlideController.php index 62603b32247..6fd77fc9ca3 100644 --- a/src/Http/Controllers/GlideController.php +++ b/src/Http/Controllers/GlideController.php @@ -113,7 +113,7 @@ private function generateBy($type, $item) $method = 'generateBy'.ucfirst($type); try { - return $this->generator->$method($item, $this->request->all()); + return $this->generator->$method($item, $this->request->query->all()); } catch (InvalidRemoteUrlException $e) { abort(400, $e->getMessage()); } catch (UnableToReadFile $e) { diff --git a/tests/Feature/Imaging/GlideControllerTest.php b/tests/Feature/Imaging/GlideControllerTest.php new file mode 100644 index 00000000000..5cda4931238 --- /dev/null +++ b/tests/Feature/Imaging/GlideControllerTest.php @@ -0,0 +1,80 @@ + true]); + + $this->generatedParams = null; + + $this->mock(ImageGenerator::class, function ($mock) { + $mock->shouldReceive('generateByPath')->andReturnUsing(function ($path, $params) { + $this->generatedParams = $params; + + return 'foo.jpg'; + }); + }); + } + + private function signedParams(array $params) + { + return SignatureFactory::create(Config::getAppKey())->addSignature('/img/foo.jpg', $params); + } + + #[Test] + public function it_uses_the_signed_query_params() + { + $params = $this->signedParams(['w' => '100']); + + $this->get('/img/foo.jpg?'.http_build_query($params)); + + $this->assertEquals($params, $this->generatedParams); + } + + #[Test] + public function it_rejects_an_invalid_signature() + { + $this->get('/img/foo.jpg?w=100&s=invalid')->assertStatus(400); + + $this->assertNull($this->generatedParams); + } + + #[Test] + public function json_body_cannot_override_signed_params() + { + $params = $this->signedParams(['w' => '100']); + + $this->call('GET', '/img/foo.jpg', $params, [], [], [ + 'CONTENT_TYPE' => 'application/json', + ], json_encode(['w' => '9999', 'mark' => 'asset::foo'])); + + $this->assertEquals($params, $this->generatedParams); + } + + #[Test] + public function method_spoofed_form_body_cannot_override_signed_params() + { + $params = $this->signedParams(['w' => '100']); + + $this->call('POST', '/img/foo.jpg?'.http_build_query($params), [ + '_method' => 'GET', + 'w' => '9999', + 'mark' => 'asset::foo', + ]); + + $this->assertEquals($params, $this->generatedParams); + } +}