request('GET', '/admin'); $csp = $client->getResponse()->headers->get('Content-Security-Policy'); self::assertNotNull($csp, 'admin SPA response must set a Content-Security-Policy'); self::assertStringContainsString("default-src 'self'", $csp); self::assertStringContainsString("object-src 'none'", $csp); self::assertStringContainsString("frame-ancestors 'none'", $csp); // ALTCHA solves its proof-of-work inside blob: Web Workers; without this // directive the login captcha errors out (worker-src falls back to script-src). self::assertStringContainsString("worker-src 'self' blob:", $csp); // The clinic-location map needs OSM tiles (img/connect), unpkg marker icons (img) // and nominatim geocoding (connect); without these the map is blank + console errors. self::assertStringContainsString('https://*.tile.openstreetmap.org', $csp); self::assertStringContainsString('https://unpkg.com', $csp); self::assertStringContainsString('https://nominatim.openstreetmap.org', $csp); } public function testNonAdminResponseDoesNotGetTheAdminCsp(): void { $client = static::createClient(); $client->request('GET', '/api/v1/doctors'); $csp = (string) $client->getResponse()->headers->get('Content-Security-Policy'); // The admin policy allows scripts from 'self'; the API policy is default-src 'none'. // Either way the admin-specific script-src must not leak onto /api. self::assertStringNotContainsString("script-src 'self'", $csp); } }