Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 100 additions & 4 deletions Classes/EventListener/PolicyMutatedEventListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Event\PolicyMutatedEvent;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Event\PolicyPreparedEvent;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\HashProxy;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\HashValue;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\RawValue;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceKeyword;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceScheme;
use TYPO3\CMS\VisualEditor\Service\EditModeService;

use function str_starts_with;

final readonly class PolicyMutatedEventListener
{
public function __construct(
Expand Down Expand Up @@ -55,14 +61,104 @@ public function __invoke(PolicyMutatedEvent $event): void

$policy = $event->getCurrentPolicy();

// we add all necessary CSP rules, so even if someone sets all Directives to 'none' we have a working Editor.

// add style-src 'unsafe-inline' to allow a working ckeditor in the frontend.
$policy = $policy->reduce(Directive::StyleSrc, SourceKeyword::nonceProxy); // to allow 'unsafe-inline' we first need to remove the nonces
$policy = $policy->reduce(Directive::StyleSrc, SourceKeyword::nonceProxy, SourceKeyword::none); // 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);
// add the same to StyleSrcAttr if that is present (fallback chain otherwise)
$policy = $policy->reduce(Directive::StyleSrcAttr, SourceKeyword::nonceProxy, SourceKeyword::none); // to allow 'unsafe-inline' we first need to remove the nonces
$policy = $policy->extend(Directive::StyleSrcAttr, SourceKeyword::unsafeInline); // no self as that is not a thing for style-src-attr
}

if ($policy->has(Directive::StyleSrcElem)) {
// add the same to StyleSrcElem if that is present (fallback chain otherwise)
$policy = $policy->reduce(Directive::StyleSrcElem, SourceKeyword::nonceProxy, SourceKeyword::none); // to allow 'unsafe-inline' we first need to remove the nonces
$policy = $policy->extend(Directive::StyleSrcElem, SourceKeyword::self, SourceKeyword::unsafeInline);
}

$policy = $policy->reduce(Directive::ScriptSrc, SourceKeyword::nonceProxy, SourceKeyword::none);
$policy = $policy->extend(Directive::ScriptSrc, SourceKeyword::self, SourceKeyword::unsafeInline);

if ($policy->has(Directive::ScriptSrcElem)) {
$policy = $policy->reduce(Directive::ScriptSrcElem, SourceKeyword::nonceProxy, SourceKeyword::none);
$policy = $policy->extend(Directive::ScriptSrcElem, SourceKeyword::self, SourceKeyword::unsafeInline);
}


if ($policy->has(Directive::ImgSrc)) {
$policy = $policy->reduce(Directive::ImgSrc, SourceKeyword::none);
$policy = $policy->extend(Directive::ImgSrc, SourceKeyword::self, SourceScheme::data);
}

if ($policy->has(Directive::FontSrc)) {
$policy = $policy->reduce(Directive::FontSrc, SourceKeyword::none);
$policy = $policy->extend(Directive::FontSrc, SourceKeyword::self);
}

if ($policy->has(Directive::FrameAncestors)) {
$policy = $policy->reduce(Directive::FrameAncestors, SourceKeyword::none);
$policy = $policy->extend(Directive::FrameAncestors, SourceKeyword::self);
}

if ($policy->has(Directive::ConnectSrc)) {
$policy = $policy->reduce(Directive::ConnectSrc, SourceKeyword::none);
$policy = $policy->extend(Directive::ConnectSrc, SourceKeyword::self);
}

if ($policy->has(Directive::TrustedTypes)) {
$policy = $policy->reduce(Directive::TrustedTypes, SourceKeyword::none);
$policy = $policy->extend(Directive::TrustedTypes, new RawValue('lit-html'));
}

if ($policy->has(Directive::RequireTrustedTypesFor)) {
$policy = $policy->remove(Directive::RequireTrustedTypesFor);
}

if ($policy->has(Directive::Sandbox)) {
$policy = $policy->remove(Directive::Sandbox);
}

// filter out all hashs and nonces
foreach (Directive::cases() as $directive) {
if (!$policy->has($directive)) {
continue;
}

$sources = [];
foreach ($policy->get($directive)->sources ?? [] as $source) {
if ($source instanceof HashValue) {
continue;
}

if ($source instanceof HashProxy) {
continue;
}

if ($source instanceof RawValue) {
if (str_starts_with((string)$source, "'sha256-")) {
continue;
}

if (str_starts_with((string)$source, "'sha384-")) {
continue;
}

if (str_starts_with((string)$source, "'sha512-")) {
continue;
}

if (str_starts_with((string)$source, "'nonce-")) {
continue;
}
}

$sources[] = $source;
}

$policy = $policy->set($directive, ...$sources);
}

$event->setCurrentPolicy($policy);
Expand Down