From e0477a62bf3f99812f6a9668a30f3469bc02162f Mon Sep 17 00:00:00 2001 From: Matthias Vogel Date: Thu, 30 Jul 2026 11:39:52 +0200 Subject: [PATCH] [BUGFIX] Restore CKEditor with hash-based CSP configuration Use nonce-based CSP behavior in edit mode so CKEditor can apply its required inline styles. Keep hash-based policies unchanged outside the Visual Editor. --- .../PolicyMutatedEventListener.php | 50 ++++++++++++++----- phpstan-baseline-13.neon | 6 +++ 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/Classes/EventListener/PolicyMutatedEventListener.php b/Classes/EventListener/PolicyMutatedEventListener.php index 02c9b41..c786e6d 100644 --- a/Classes/EventListener/PolicyMutatedEventListener.php +++ b/Classes/EventListener/PolicyMutatedEventListener.php @@ -5,17 +5,40 @@ namespace TYPO3\CMS\VisualEditor\EventListener; use TYPO3\CMS\Core\Attribute\AsEventListener; +use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Event\PolicyMutatedEvent; -use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Mutation; -use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationMode; +use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Event\PolicyPreparedEvent; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceKeyword; use TYPO3\CMS\VisualEditor\Service\EditModeService; final readonly class PolicyMutatedEventListener { - public function __construct(private EditModeService $editModeService) + public function __construct( + private EditModeService $editModeService, + private Typo3Version $typo3Version, + ) { + } + + #[AsEventListener] + public function policyPreparedEvent(PolicyPreparedEvent $event): void { + if (!$this->editModeService->isEditMode($event->request)) { + return; + } + + if ($this->typo3Version->getMajorVersion() <= 13) { + // hash is a TYPO3 >= 14 feature only + return; + } + + // until the BE context is using hashs we should enforce the usage if hashs (only in edit mode) + // the ckeditor is not working nicely with CSP hashes so we need to use nonce for now. + // as the Visual Editor disables the cache the main benefit of hash's are not important here. + if ($event->policyBag->behavior->useHash === true) { + $event->policyBag->behavior->useHash = false; + $event->policyBag->behavior->useNonce = true; + } } #[AsEventListener] @@ -30,17 +53,18 @@ public function __invoke(PolicyMutatedEvent $event): void return; } + $policy = $event->getCurrentPolicy(); + // add style-src 'unsafe-inline' to allow a working ckeditor in the frontend. - $mutation = new Mutation(MutationMode::Reduce, Directive::StyleSrc, SourceKeyword::nonceProxy); - $event->getCurrentPolicy()->mutate($mutation); - $mutation = new Mutation(MutationMode::Extend, Directive::StyleSrc, SourceKeyword::self, SourceKeyword::unsafeInline); - $event->getCurrentPolicy()->mutate($mutation); - - if ($event->getCurrentPolicy()->get(Directive::StyleSrcAttr)) { - $mutation = new Mutation(MutationMode::Reduce, Directive::StyleSrcAttr, SourceKeyword::nonceProxy); - $event->getCurrentPolicy()->mutate($mutation); - $mutation = new Mutation(MutationMode::Extend, Directive::StyleSrcAttr, SourceKeyword::unsafeInline); - $event->getCurrentPolicy()->mutate($mutation); + $policy = $policy->reduce(Directive::StyleSrc, SourceKeyword::nonceProxy); // to allow 'unsafe-inline' we first need to remove the nonces + $policy = $policy->extend(Directive::StyleSrc, SourceKeyword::self, SourceKeyword::unsafeInline); + + if ($policy->has(Directive::StyleSrcAttr)) { + // add the same to StyleSrcAttr if that is present + $policy = $policy->reduce(Directive::StyleSrcAttr, SourceKeyword::nonceProxy); // to allow 'unsafe-inline' we first need to remove the nonces + $policy = $policy->extend(Directive::StyleSrcAttr, SourceKeyword::unsafeInline); } + + $event->setCurrentPolicy($policy); } } diff --git a/phpstan-baseline-13.neon b/phpstan-baseline-13.neon index 28ebb11..9ad3900 100644 --- a/phpstan-baseline-13.neon +++ b/phpstan-baseline-13.neon @@ -240,6 +240,12 @@ parameters: count: 1 path: Classes/Core/RichtText/RichTextConfigurationService.php + - + message: '#^Access to an undefined property TYPO3\\CMS\\Core\\Security\\ContentSecurityPolicy\\Configuration\\Behavior\:\:\$useHash\.$#' + identifier: property.notFound + count: 1 + path: Classes/EventListener/PolicyMutatedEventListener.php + - message: '#^Call to method getContentArea\(\) on an unknown class TYPO3\\CMS\\Fluid\\Event\\ModifyRenderedContentAreaEvent\.$#' identifier: class.notFound