attributes->set('csp_script_nonce', $scriptNonce); $request->attributes->set('csp_style_nonce', $styleNonce); View::share('cspNonce', $scriptNonce); View::share('cspStyleNonce', $styleNonce); Vite::useCspNonce($scriptNonce); $response = $next($request); if (app()->environment('local') || config('app.debug')) { return $response; } if ($response->headers->has('Content-Security-Policy')) { return $response; } $allowUnsafeInlineStyles = $request->is('event-admin*'); $matomoOrigin = $this->normaliseOrigin(config('services.matomo.url')); $scriptSources = [ "'self'", "'nonce-{$scriptNonce}'", 'https://cdn.paddle.com', 'https://global.localizecdn.com', ]; $styleSources = [ "'self'", "'nonce-{$styleNonce}'", 'https:', ]; $connectSources = [ "'self'", 'https://api.paddle.com', 'https://sandbox-api.paddle.com', 'https://checkout.paddle.com', 'https://sandbox-checkout.paddle.com', 'https://checkout-service.paddle.com', 'https://sandbox-checkout-service.paddle.com', 'https://global.localizecdn.com', ]; $frameSources = [ "'self'", 'https://checkout.paddle.com', 'https://sandbox-checkout.paddle.com', 'https://checkout-service.paddle.com', 'https://sandbox-checkout-service.paddle.com', ]; $imgSources = [ "'self'", 'data:', 'blob:', 'https:', ]; $fontSources = [ "'self'", 'data:', 'https:', ]; $mediaSources = [ "'self'", 'data:', 'blob:', 'https:', ]; if ($matomoOrigin) { $scriptSources[] = $matomoOrigin; $connectSources[] = $matomoOrigin; $imgSources[] = $matomoOrigin; } $isDev = app()->environment(['local', 'development']) || config('app.debug'); if ($isDev) { $devHosts = [ 'http://fotospiel-app.test:5173', 'http://127.0.0.1:5173', 'https://localhost:5173', 'https://127.0.0.1:5173', ]; $wsHosts = [ 'ws://fotospiel-app.test:5173', 'ws://127.0.0.1:5173', 'wss://localhost:5173', 'wss://127.0.0.1:5173', ]; $scriptSources = array_merge($scriptSources, $devHosts, ["'unsafe-inline'", "'unsafe-eval'"]); $styleSources = array_merge($styleSources, $devHosts, ["'unsafe-inline'"]); $connectSources = array_merge($connectSources, $devHosts, $wsHosts); $fontSources = array_merge($fontSources, $devHosts); $mediaSources = array_merge($mediaSources, $devHosts); } $styleSources[] = 'data:'; $connectSources[] = 'https:'; $fontSources[] = 'https:'; $styleElemSources = $styleSources; if ($allowUnsafeInlineStyles) { $styleElemSources = array_unique(array_merge($styleElemSources, ["'unsafe-inline'"])); } $directives = [ 'default-src' => ["'self'"], 'script-src' => array_unique($scriptSources), 'style-src' => array_unique($styleSources), 'style-src-elem' => $styleElemSources, 'style-src-attr' => ["'unsafe-inline'"], 'img-src' => array_unique($imgSources), 'font-src' => array_unique($fontSources), 'connect-src' => array_unique($connectSources), 'media-src' => array_unique($mediaSources), 'frame-src' => array_unique($frameSources), 'form-action' => ["'self'"], 'base-uri' => ["'self'"], 'object-src' => ["'none'"], 'frame-ancestors' => ["'self'"], ]; $csp = collect($directives) ->map(fn ($values, $directive) => $directive.' '.implode(' ', array_filter($values))) ->implode('; '); $response->headers->set('Content-Security-Policy', $csp); return $response; } private function normaliseOrigin(?string $url): ?string { if (! $url) { return null; } $parsed = parse_url($url); if (! $parsed || ! isset($parsed['scheme'], $parsed['host'])) { return null; } $origin = strtolower($parsed['scheme'].'://'.$parsed['host']); if (isset($parsed['port'])) { $origin .= ':'.$parsed['port']; } return $origin; } }