['onRequest', 300], KernelEvents::RESPONSE => ['onResponse', -300]]; } private function isAdmin(string $path): bool { return 1 === preg_match('#^/'.preg_quote($this->adminPath, '#').'(?:/|$)#', $path); } /** * One value per administration page, for the single inline script there is: the import map. * * It has to be inline - the specification knows no external import maps - and it has to work * under `script-src 'self'`. Without it the map is dropped without a word, the modules load * without a version key, and a year-old `editor-model.js` out of the cache meets a new * `editor.js`. That is not a theory: it broke the editor after 1.5.0 went out. */ public function onRequest(RequestEvent $event): void { $request = $event->getRequest(); if (!$event->isMainRequest() || !$this->isAdmin($request->getPathInfo())) { return; } $request->attributes->set('csp_nonce', rtrim(strtr(base64_encode(random_bytes(16)), '+/', '-_'), '=')); } public function onResponse(ResponseEvent $event): void { if (!$this->isAdmin($event->getRequest()->getPathInfo())) { return; } $headers = $event->getResponse()->headers; $headers->set('Cache-Control', 'private, no-store'); $headers->set('X-Robots-Tag', 'noindex, nofollow'); $headers->set('X-Content-Type-Options', 'nosniff'); $headers->set('Referrer-Policy', 'same-origin'); $headers->set('Permissions-Policy', 'accelerometer=(), camera=(), display-capture=(), geolocation=(), gyroscope=(), microphone=(), payment=(), usb=()'); // The preview must show the same typeface as the published page, so the font hosts are // allowed here as well. Everything else stays as narrow as on the public side. // The nonce only ever adds the one script it names; 'self' keeps applying to everything // else, so nothing is loosened by it. $nonce = $event->getRequest()->attributes->get('csp_nonce'); $script = is_string($nonce) && '' !== $nonce ? sprintf("'self' 'nonce-%s'", $nonce) : "'self'"; $headers->set('Content-Security-Policy', "default-src 'self'; script-src {$script}; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' https:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'"); } }