diff --git a/api/tests/Feature/Security/CspConnectsToObservabilityTest.php b/api/tests/Feature/Security/CspConnectsToObservabilityTest.php
new file mode 100644
index 00000000..eb569899
--- /dev/null
+++ b/api/tests/Feature/Security/CspConnectsToObservabilityTest.php
@@ -0,0 +1,109 @@
+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('/
Crewli — Organizer
-
+
+ content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' http://localhost:8000 ws://localhost:5174 http://localhost:8200; form-action 'self'; base-uri 'self'">
diff --git a/deploy/nginx/csp-spa.conf b/deploy/nginx/csp-spa.conf
index d4a89894..76c2f5ce 100644
--- a/deploy/nginx/csp-spa.conf
+++ b/deploy/nginx/csp-spa.conf
@@ -2,14 +2,18 @@
# Vite bundles all JS/CSS into same-origin files.
# 'unsafe-inline' for style-src is required by Vuetify (inline styles for theming).
# img-src https: allows organisation logos loaded from external URLs.
-# connect-src must include the API domain for XHR/fetch calls.
+# connect-src must include:
+# - https://api.crewli.app (XHR/fetch to the API)
+# - https://monitoring.hausdesign.nl (RFC-WS-7 §3.5: GlitchTip event ingest;
+# without it the browser silently blocks
+# every @sentry/vue POST)
#
# IMPORTANT: Start with Content-Security-Policy-Report-Only to catch
# false positives. Switch to Content-Security-Policy after 1-2 weeks
# of clean logs.
# Report-only mode (start with this):
-# add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' https://api.crewli.app; frame-ancestors 'none'; form-action 'self'; base-uri 'self'" always;
+# add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' https://api.crewli.app https://monitoring.hausdesign.nl; frame-ancestors 'none'; form-action 'self'; base-uri 'self'" always;
# Enforce mode (switch to this after testing):
-add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' https://api.crewli.app; frame-ancestors 'none'; form-action 'self'; base-uri 'self'" always;
+add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' https://api.crewli.app https://monitoring.hausdesign.nl; frame-ancestors 'none'; form-action 'self'; base-uri 'self'" always;
diff --git a/dev-docs/SECURITY_AUDIT.md b/dev-docs/SECURITY_AUDIT.md
index c57e3d34..84d8b370 100644
--- a/dev-docs/SECURITY_AUDIT.md
+++ b/dev-docs/SECURITY_AUDIT.md
@@ -610,6 +610,7 @@ Audit scope: all files under `api/` and `apps/` (app, portal).
- **Description:** ~~Neither app set a CSP meta tag or header.~~
- **Risk:** Injected scripts have unrestricted access.
- **Resolution:** API CSP enforced via `SecurityHeaders` middleware (`default-src 'none'; frame-ancestors 'none'`). SPA CSP configured via Nginx snippets (`deploy/nginx/csp-spa.conf`, `csp-portal.conf`). Dev CSP meta tags added to all `index.html` files for local testing. See `deploy/README.md` for rollout instructions.
+- **WS-7 follow-up (mei 2026):** SPA `connect-src` whitelists the GlitchTip event-ingest endpoint as an explicit security control — dev `http://localhost:8200`, prod `https://monitoring.hausdesign.nl` (RFC-WS-7 §3.5). This restricts outgoing observability traffic to a single known host; without it, the strict CSP would either silently drop events (PR-3 regression) or — if loosened blindly — allow exfiltration to arbitrary hosts. Regression-guard: `tests/Feature/Security/CspConnectsToObservabilityTest.php` reads both the dev meta tag and the production nginx config and asserts the host is present.
#### [LOW] A13-10: No hardcoded secrets found in frontend code (positive)