get(). Caught a real PR-3 regression: frontend SDK * emitted events correctly, but Crewli's strict CSP blocked egress at * the browser. Without this guard, the bug would resurface every time * a CSP refactor happens. */ final class CspConnectsToObservabilityTest extends TestCase { private const DEV_GLITCHTIP_HOST = 'http://localhost:8200'; private const PROD_GLITCHTIP_HOST = 'https://monitoring.hausdesign.nl'; public function test_dev_meta_csp_whitelists_localhost_glitchtip(): void { $html = file_get_contents(base_path('../apps/app/index.html')); $this->assertNotFalse($html, 'apps/app/index.html must be readable'); $csp = $this->extractMetaCsp($html); $this->assertNotNull($csp, 'apps/app/index.html must declare a meta CSP'); $connectSrc = $this->extractDirective($csp, 'connect-src'); $this->assertNotNull($connectSrc, 'meta CSP must define connect-src'); $this->assertStringContainsString( self::DEV_GLITCHTIP_HOST, $connectSrc, sprintf( 'Dev meta CSP connect-src must whitelist %s for the local GlitchTip stack (RFC-WS-7 §3.5). ' .'Without it the browser blocks @sentry/vue events with: "Refused to connect because it ' .'violates the following Content Security Policy directive: connect-src ...". ' .'Found connect-src: %s', self::DEV_GLITCHTIP_HOST, $connectSrc, ), ); } public function test_prod_nginx_csp_whitelists_monitoring_host(): void { $conf = file_get_contents(base_path('../deploy/nginx/csp-spa.conf')); $this->assertNotFalse($conf, 'deploy/nginx/csp-spa.conf must be readable'); $matches = []; $found = preg_match_all('/^\s*add_header\s+Content-Security-Policy(?:-Report-Only)?\s+"([^"]+)"\s+always;/m', $conf, $matches); $this->assertNotEmpty($found, 'csp-spa.conf must contain at least one add_header Content-Security-Policy directive'); // Every uncommented add_header line must include the GlitchTip host // in connect-src. Multiple lines exist (Report-Only + enforce); both // need the whitelist or a refactor that flips between them silently // breaks observability. foreach ($matches[1] as $cspValue) { $connectSrc = $this->extractDirective($cspValue, 'connect-src'); $this->assertNotNull($connectSrc, "csp-spa.conf CSP must define connect-src. Got: {$cspValue}"); $this->assertStringContainsString( self::PROD_GLITCHTIP_HOST, $connectSrc, sprintf( 'Prod nginx CSP connect-src must whitelist %s (RFC-WS-7 §3.5). Found: %s', self::PROD_GLITCHTIP_HOST, $connectSrc, ), ); } } private function extractMetaCsp(string $html): ?string { if (preg_match('/