54 lines
1.9 KiB
PHP
54 lines
1.9 KiB
PHP
<?php
|
|
|
|
namespace App\Shared\EventSubscriber;
|
|
|
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
|
use Symfony\Component\HttpKernel\Event\ResponseEvent;
|
|
use Symfony\Component\HttpKernel\KernelEvents;
|
|
|
|
/**
|
|
* Adds a Content-Security-Policy to the admin SPA responses (/admin/*).
|
|
* The /api/* surface already sends `default-src 'none'`; the SPA had none, so a
|
|
* DOM-injected script had no second line of defence. Scoped to /admin only to
|
|
* avoid breaking the strict API CSP.
|
|
*/
|
|
final class AdminCspSubscriber implements EventSubscriberInterface
|
|
{
|
|
public static function getSubscribedEvents(): array
|
|
{
|
|
return [KernelEvents::RESPONSE => 'onResponse'];
|
|
}
|
|
|
|
public function onResponse(ResponseEvent $event): void
|
|
{
|
|
if (!$event->isMainRequest()) {
|
|
return;
|
|
}
|
|
|
|
if (!str_starts_with($event->getRequest()->getPathInfo(), '/admin')) {
|
|
return;
|
|
}
|
|
|
|
$response = $event->getResponse();
|
|
if ($response->headers->has('Content-Security-Policy')) {
|
|
return;
|
|
}
|
|
|
|
$response->headers->set(
|
|
'Content-Security-Policy',
|
|
"default-src 'self'; "
|
|
. "script-src 'self'; "
|
|
. "worker-src 'self' blob:; " // ALTCHA proof-of-work solver runs in blob: Web Workers
|
|
. "style-src 'self' 'unsafe-inline'; " // Tailwind / CKEditor inline styles
|
|
// OpenStreetMap tiles + unpkg Leaflet marker icons render the clinic-location map
|
|
. "img-src 'self' data: blob: https://*.tile.openstreetmap.org https://unpkg.com; "
|
|
. "font-src 'self' data:; "
|
|
// nominatim geocoding + OSM tile fetches for the clinic-location map
|
|
. "connect-src 'self' https://nominatim.openstreetmap.org https://*.tile.openstreetmap.org; "
|
|
. "frame-ancestors 'none'; "
|
|
. "base-uri 'self'; "
|
|
. "object-src 'none'"
|
|
);
|
|
}
|
|
}
|