Content-Security-Policy (CSP) to nagłówek HTTP, który chroni stronę przed atakami XSS i innymi zagrożeniami. Jednak zbyt restrykcyjny CSP może zablokować usługi Google jak GTM, Analytics czy Maps. Ten przewodnik pokazuje jak skonfigurować CSP, aby działały wszystkie potrzebne usługi.

Czym jest Content-Security-Policy?

CSP definiuje dozwolone źródła dla różnych typów zasobów (skrypty, style, obrazy, fonty). Przeglądarka blokuje wszystko, co nie jest jawnie dozwolone.

Podstawowa składnia

Content-Security-Policy: directive source1 source2; directive2 source3;

Główne dyrektywy

DyrektywaKontroluje
default-srcDomyślne źródło dla wszystkich typów
script-srcSkrypty JavaScript
style-srcStyle CSS
img-srcObrazy
font-srcFonty
connect-srcXHR, fetch, WebSocket
frame-srcIframes
object-srcPluginy (Flash, Java)

Wartości źródeł

WartośćZnaczenie
'self'Ta sama domena
'none'Blokuj wszystko
'unsafe-inline'Dozwól inline (niebezpieczne!)
'unsafe-eval'Dozwól eval() (niebezpieczne!)
'nonce-xyz'Tylko skrypty z tym nonce
'strict-dynamic'Zaufaj skryptom ładowanym przez zaufane
https:Wszystkie źródła HTTPS
*.example.comSubdomena

CSP dla Google Tag Manager

GTM wymaga wielu domen i funkcjonalności. Konfiguracja zależy od trybu (client-side vs server-side).

GTM Client-Side - minimalna konfiguracja

Content-Security-Policy:
  script-src 'self' 'unsafe-inline' 'unsafe-eval'
    https://www.googletagmanager.com
    https://tagmanager.google.com;
  img-src 'self' data:
    https://www.googletagmanager.com
    https://www.google-analytics.com
    https://ssl.gstatic.com
    https://www.gstatic.com;
  connect-src 'self'
    https://www.google-analytics.com
    https://analytics.google.com
    https://stats.g.doubleclick.net
    https://region1.google-analytics.com;
  frame-src
    https://www.googletagmanager.com;

Problem: GTM wymaga unsafe-inline i unsafe-eval

GTM dynamicznie wstrzykuje skrypty, co wymaga 'unsafe-inline'. Custom HTML tags mogą używać eval(). To znacząco osłabia CSP.

Rozwiązanie 1: Nonce dla GTM

<!-- Generuj nonce po stronie serwera -->
<script nonce="abc123">
  (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
  new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
  j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;
  j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;
  j.nonce='abc123';
  f.parentNode.insertBefore(j,f);
  })(window,document,'script','dataLayer','GTM-XXXXX');
</script>
Content-Security-Policy:
  script-src 'self' 'nonce-abc123' 'strict-dynamic'
    https://www.googletagmanager.com;

Rozwiązanie 2: Server-Side GTM

Server-side GTM znacząco upraszcza CSP:

Content-Security-Policy:
  script-src 'self' 'nonce-abc123'
    https://gtm.yourdomain.com;
  connect-src 'self'
    https://gtm.yourdomain.com;

CSP dla Google Analytics 4

GA4 z gtag.js

Content-Security-Policy:
  script-src 'self'
    https://www.googletagmanager.com;
  img-src 'self'
    https://www.google-analytics.com
    https://www.googletagmanager.com;
  connect-src 'self'
    https://www.google-analytics.com
    https://analytics.google.com
    https://region1.google-analytics.com
    https://region2.google-analytics.com
    https://region3.google-analytics.com;

GA4 z GTM

Jeśli ładujesz GA4 przez GTM, potrzebujesz kombinacji powyższych.

CSP dla Google Maps

Maps JavaScript API

Content-Security-Policy:
  script-src 'self' 'unsafe-inline'
    https://maps.googleapis.com
    https://maps.gstatic.com;
  img-src 'self' data: blob:
    https://maps.googleapis.com
    https://maps.gstatic.com
    https://*.ggpht.com
    https://*.google.com
    https://*.googleapis.com;
  style-src 'self' 'unsafe-inline'
    https://fonts.googleapis.com;
  font-src 'self'
    https://fonts.gstatic.com;
  connect-src 'self'
    https://maps.googleapis.com
    https://places.googleapis.com;
  frame-src
    https://www.google.com;
  worker-src blob:;

Maps Embed API (iframe)

Content-Security-Policy:
  frame-src
    https://www.google.com
    https://maps.google.com;

CSP dla Google Fonts

Content-Security-Policy:
  style-src 'self'
    https://fonts.googleapis.com;
  font-src 'self'
    https://fonts.gstatic.com;

Alternatywa: Self-hosting fontów

Hostując fonty lokalnie, eliminujesz zewnętrzne zależności:

Content-Security-Policy:
  font-src 'self';
  style-src 'self';

CSP dla Google reCAPTCHA

reCAPTCHA v2/v3

Content-Security-Policy:
  script-src 'self'
    https://www.google.com/recaptcha/
    https://www.gstatic.com/recaptcha/;
  frame-src
    https://www.google.com/recaptcha/
    https://recaptcha.google.com;
  style-src 'self' 'unsafe-inline';

CSP dla YouTube embeds

Content-Security-Policy:
  frame-src
    https://www.youtube.com
    https://www.youtube-nocookie.com;
  img-src 'self'
    https://i.ytimg.com
    https://img.youtube.com;

CSP dla Google Ads

Content-Security-Policy:
  script-src 'self' 'unsafe-inline'
    https://www.googleadservices.com
    https://www.googletagmanager.com
    https://googleads.g.doubleclick.net;
  img-src 'self'
    https://www.google.com
    https://www.google.pl
    https://googleads.g.doubleclick.net
    https://www.googleadservices.com;
  connect-src 'self'
    https://www.google.com
    https://pagead2.googlesyndication.com;
  frame-src
    https://bid.g.doubleclick.net
    https://tpc.googlesyndication.com;

Google AdSense

Content-Security-Policy:
  script-src 'self' 'unsafe-inline' 'unsafe-eval'
    https://pagead2.googlesyndication.com
    https://adservice.google.com
    https://www.googletagservices.com
    https://partner.googleadservices.com;
  img-src 'self' data:
    https://pagead2.googlesyndication.com
    https://tpc.googlesyndication.com
    https://*.google.com;
  frame-src
    https://googleads.g.doubleclick.net
    https://tpc.googlesyndication.com
    https://www.google.com;
  style-src 'self' 'unsafe-inline';

Kompletna konfiguracja dla typowej strony

Strona z GTM, GA4, Maps i Fonts

Content-Security-Policy:
  default-src 'self';

  script-src 'self' 'unsafe-inline' 'unsafe-eval'
    https://www.googletagmanager.com
    https://tagmanager.google.com
    https://www.google-analytics.com
    https://maps.googleapis.com
    https://maps.gstatic.com;

  style-src 'self' 'unsafe-inline'
    https://fonts.googleapis.com
    https://tagmanager.google.com;

  img-src 'self' data: blob:
    https://www.googletagmanager.com
    https://www.google-analytics.com
    https://ssl.gstatic.com
    https://www.gstatic.com
    https://maps.googleapis.com
    https://maps.gstatic.com
    https://*.ggpht.com
    https://*.google.com;

  font-src 'self'
    https://fonts.gstatic.com;

  connect-src 'self'
    https://www.google-analytics.com
    https://analytics.google.com
    https://region1.google-analytics.com
    https://stats.g.doubleclick.net
    https://maps.googleapis.com;

  frame-src
    https://www.googletagmanager.com
    https://www.google.com
    https://www.youtube.com;

  object-src 'none';
  base-uri 'self';
  form-action 'self';

Implementacja CSP

Apache (.htaccess)

Header set Content-Security-Policy "default-src 'self'; script-src 'self' https://www.googletagmanager.com; ..."

Nginx

add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://www.googletagmanager.com; ..." always;

Node.js (Express)

const helmet = require('helmet');

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "https://www.googletagmanager.com"],
    styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
    imgSrc: ["'self'", "data:", "https://www.google-analytics.com"],
    fontSrc: ["'self'", "https://fonts.gstatic.com"],
    connectSrc: ["'self'", "https://www.google-analytics.com"],
    frameSrc: ["https://www.googletagmanager.com"]
  }
}));

Netlify (netlify.toml)

[[headers]]
  for = "/*"
  [headers.values]
    Content-Security-Policy = "default-src 'self'; script-src 'self' https://www.googletagmanager.com; ..."

Vercel (vercel.json)

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Content-Security-Policy",
          "value": "default-src 'self'; script-src 'self' https://www.googletagmanager.com; ..."
        }
      ]
    }
  ]
}

Testowanie CSP

1. Report-Only Mode

Zacznij od trybu raportowania, który nie blokuje, tylko loguje:

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report;

2. Przeglądarka DevTools

Console pokaże błędy CSP:

Refused to load the script 'https://example.com/script.js' because it violates the Content Security Policy directive: "script-src 'self'".

3. CSP Evaluator

https://csp-evaluator.withgoogle.com/ - narzędzie Google do analizy CSP.

4. Observatory by Mozilla

https://observatory.mozilla.org/ - kompleksowy audyt bezpieczeństwa.

Raportowanie naruszeń CSP

Endpoint raportowania

Content-Security-Policy: default-src 'self'; report-uri /csp-report; report-to csp-endpoint;

Odbieranie raportów (Node.js)

app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
  console.log('CSP Violation:', req.body['csp-report']);
  res.status(204).end();
});

Best Practices

1. Zacznij restrykcyjnie

Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self';

Dodawaj źródła dopiero gdy są potrzebne.

2. Unikaj unsafe-inline i unsafe-eval

Jeśli to możliwe, używaj nonce lub hash zamiast 'unsafe-inline':

<script nonce="random123">
  // Twój kod
</script>
Content-Security-Policy: script-src 'nonce-random123';

3. Używaj strict-dynamic

'strict-dynamic' pozwala zaufanym skryptom ładować kolejne:

Content-Security-Policy: script-src 'nonce-abc123' 'strict-dynamic';

4. Rozważ Server-Side GTM

Server-side tagging drastycznie upraszcza CSP i zwiększa bezpieczeństwo.

5. Regularnie aktualizuj

Google może zmieniać domeny. Monitoruj raporty CSP i aktualizuj politykę.

Podsumowanie

CSP dla Google Services wymaga balansowania między bezpieczeństwem a funkcjonalnością:

  1. GTM wymaga kompromisów - 'unsafe-inline' jest często niezbędne
  2. Server-side GTM znacząco upraszcza CSP
  3. Nonce + strict-dynamic to najlepsza praktyka
  4. Testuj w Report-Only przed wdrożeniem
  5. Monitoruj naruszenia przez raportowanie

Dobrze skonfigurowany CSP chroni użytkowników przed XSS bez blokowania funkcjonalności strony.

Źródła

  1. MDN - Content-Security-Policy https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

  2. Google - CSP for Google Tag Manager https://developers.google.com/tag-platform/tag-manager/csp

  3. Google - CSP Evaluator https://csp-evaluator.withgoogle.com/

  4. web.dev - Content Security Policy https://web.dev/articles/csp

  5. Google Maps Platform - CSP https://developers.google.com/maps/documentation/javascript/content-security-policy