Content-Security-Policy (CSP) to nagłówek HTTP, który chroni witrynę przed atakami XSS i innymi zagrożeniami. Zbyt restrykcyjna polityka potrafi jednak zablokować usługi Google — GTM, Analytics czy Maps. Ten przewodnik pokazuje, jak skonfigurować CSP tak, aby wszystkie potrzebne usługi działały.

TL;DR — gotowy nagłówek CSP dla typowej strony

Jeśli potrzebujesz od razu działającego nagłówka CSP dla witryny z GTM, GA4, Google Maps, Fonts, reCAPTCHA i YouTube, zacznij od tego i dostosuj:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-RANDOM' 'strict-dynamic'
    https://www.googletagmanager.com
    https://www.google-analytics.com
    https://maps.googleapis.com
    https://www.google.com/recaptcha/
    https://www.gstatic.com/recaptcha/;
  style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
  font-src 'self' https://fonts.gstatic.com;
  img-src 'self' data: blob:
    https://*.google-analytics.com
    https://*.googletagmanager.com
    https://*.gstatic.com
    https://*.googleapis.com
    https://*.ggpht.com
    https://i.ytimg.com;
  connect-src 'self'
    https://*.google-analytics.com
    https://*.analytics.google.com
    https://*.googletagmanager.com
    https://stats.g.doubleclick.net
    https://maps.googleapis.com;
  frame-src
    https://www.googletagmanager.com
    https://www.google.com
    https://www.youtube.com
    https://www.youtube-nocookie.com;
  worker-src 'self' blob:;
  object-src 'none';
  base-uri 'self';
  form-action 'self';

Zastąp RANDOM noncem generowanym per-request po stronie serwera. Zanim włączysz egzekwowanie, uruchom politykę w trybie Content-Security-Policy-Report-Only, żeby zobaczyć, co Twoja prawdziwa strona faktycznie ładuje.

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: dyrektywa źródło1 źródło2; dyrektywa2 źródło3;

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'. Tagi Custom HTML 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.twojadomena.pl;
  connect-src 'self'
    https://gtm.twojadomena.pl;

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 przez GTM

Jeśli ładujesz GA4 przez GTM, potrzebujesz kombinacji powyższych reguł. Zobacz też dobre praktyki dataLayer — poprawnie zaprojektowany dataLayer pozwala ograniczyć liczbę Custom HTML tagów, co bezpośrednio upraszcza politykę CSP.

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 osadzeń YouTube

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;

Pułapka 1: sam youtube-nocookie.com nie wystarczy

Wiele stron używa youtube-nocookie.com ze względu na prywatność — i dodaje do whitelisty tylko tę domenę w frame-src. To rozbija embed w kilku sytuacjach:

  • Overlay z polecanymi filmami na końcu klipu przekierowuje na youtube.com.
  • Linki do kanału i logo w pasku playera otwierają youtube.com.
  • Część zasobów chrome playera i tak ładuje się z youtube.com niezależnie od tego, którą domenę osadzasz.

Rozwiązanie: zawsze dodawaj do whitelisty zarówno https://www.youtube-nocookie.com, jak i https://www.youtube.com w frame-src, nawet jeśli Twój <iframe src> wskazuje tylko na wersję bez cookies.

Pułapka 2: domeny miniaturek dla lite-embeds

Jeśli używasz wzorca “facade” (klikalna miniatura zamiast pełnego iframe) — popularne rozwiązanie dla wydajności — obrazek miniatury ładuje się z innej domeny niż sam player:

  • https://i.ytimg.com — domyślny host miniaturek (vi/VIDEO_ID/maxresdefault.jpg).
  • https://img.youtube.com — starszy alias, wciąż używany przez niektóre biblioteki.

Obie muszą być w img-src, inaczej podgląd po cichu się nie załaduje i użytkownik zobaczy pusty placeholder.

Pułapka 3: Cross-Origin-Opener-Policy łamie YouTube

Jeśli zabezpieczasz witrynę nagłówkiem Cross-Origin-Opener-Policy: same-origin, iframe YouTube traci dostęp do popupów, które otwiera (logowanie, udostępnianie, full-screen). Player sprawia wrażenie, że działa, ale niektóre interakcje po cichu zawodzą i nie widać żadnego błędu CSP w konsoli. Rozwiązanie: przełącz na Cross-Origin-Opener-Policy: same-origin-allow-popups.

Po pełne wyjaśnienie COOP, COEP, CORP, crossOriginIsolated oraz jak odblokować SharedArrayBuffer bez łamania integracji third-party zobacz dedykowany przewodnik: COOP, COEP, CORP — cross-origin isolation po polsku.

Gotowa produkcyjna konfiguracja

Oto dokładny fragment, którego używamy na uper.pl — łączy GTM + GA4 + osadzenia YouTube w trybie no-cookie w jednym nagłówku:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'unsafe-inline' 'unsafe-eval'
    https://www.googletagmanager.com
    https://tagmanager.google.com;
  style-src 'self' 'unsafe-inline'
    https://cdn.jsdelivr.net;
  font-src 'self'
    https://cdn.jsdelivr.net;
  img-src 'self' data:
    https://www.googletagmanager.com
    https://www.google-analytics.com
    https://ssl.gstatic.com
    https://www.gstatic.com
    https://i.ytimg.com;
  connect-src 'self'
    https://www.google-analytics.com
    https://analytics.google.com
    https://stats.g.doubleclick.net
    https://*.google-analytics.com
    https://*.analytics.google.com
    https://*.googletagmanager.com;
  frame-src
    https://www.googletagmanager.com
    https://www.youtube-nocookie.com
    https://www.youtube.com;
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'self';

W parze z Cross-Origin-Opener-Policy: same-origin-allow-popups oraz X-Frame-Options: SAMEORIGIN ta kombinacja przechodzi audyt Mozilla Observatory z oceną A+, a osadzenia YouTube działają bez zarzutu.

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';

Consent Mode v2 jest obowiązkowy dla ruchu z UE od marca 2024. Używa tych samych domen co GTM + GA4, ale wprowadza dwa niuanse:

  1. Cookiebot, Iubenda, OneTrust i inne CMP ładują się z własnych domen — dodaj do whitelisty tę, której używasz.
  2. Sygnały zgody są wysyłane, zanim użytkownik wyrazi zgodę (w stanie denied), więc connect-src do Google musi być otwarty od pierwszego wejścia na stronę, a nie dopiero po akceptacji.
Content-Security-Policy:
  script-src 'self' 'nonce-abc123'
    https://www.googletagmanager.com
    https://consent.cookiebot.com
    https://consentcdn.cookiebot.com;
  connect-src 'self'
    https://consent.cookiebot.com
    https://consentcdn.cookiebot.com
    https://www.google-analytics.com
    https://region1.google-analytics.com;
  frame-src
    https://consent.cookiebot.com
    https://consentcdn.cookiebot.com;

Zobacz przewodnik wdrażania Consent Mode v2 oraz jak zweryfikować, że działa poprawnie.

CSP dla Google Identity Services (GIS) / One Tap

Google Identity Services zastąpiły starą bibliotekę gapi.auth2. Logowanie Google, One Tap oraz FedCM API używają tych domen:

Content-Security-Policy:
  script-src 'self'
    https://accounts.google.com/gsi/client;
  connect-src 'self'
    https://accounts.google.com/gsi/
    https://accounts.google.com/.well-known/;
  frame-src
    https://accounts.google.com/gsi/
    https://accounts.google.com/o/oauth2/;
  style-src 'self' 'unsafe-inline'
    https://accounts.google.com/gsi/style;

Typowa pułapka: FedCM wymaga frame-ancestors po stronie Google, nie Twojej. Jeśli One Tap po cichu się nie renderuje, sprawdź accounts.google.com w zakładce Network — błąd 403 zwykle oznacza, że Twoja strona nie znajduje się w authorized origins klienta OAuth w Google Cloud, a nie problem z CSP.

CSP dla Firebase (Auth, Firestore, Hosting)

Firebase obejmuje kilka usług Google. Każdy produkt wymaga własnego zestawu wpisów:

Content-Security-Policy:
  script-src 'self'
    https://www.gstatic.com/firebasejs/
    https://apis.google.com
    https://www.googleapis.com;
  connect-src 'self'
    https://*.firebaseio.com
    https://*.firebaseapp.com
    https://firestore.googleapis.com
    https://identitytoolkit.googleapis.com
    https://securetoken.googleapis.com
    wss://*.firebaseio.com;
  frame-src
    https://*.firebaseapp.com;

Firebase Auth używa signInWithRedirect, który otwiera __/auth/handler na Twojej subdomenie Firebase Hosting — pamiętaj, aby dodać ją do frame-src.

Kompletna konfiguracja dla typowej witryny

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. Tryb Report-Only

Zacznij od trybu raportowania, który nie blokuje, a jedynie loguje naruszenia:

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

2. DevTools przeglądarki

Konsola 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 raportujący

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();
});

Dobre praktyki

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ę.

Rozwiązywanie problemów — typowe błędy CSP i ich naprawa

Poniżej dokładne komunikaty, które zobaczysz w konsoli DevTools Chrome / Firefox, oraz co dodać do polityki, żeby je naprawić.

Refused to load the script 'https://www.googletagmanager.com/gtm.js'

Przyczyna: script-src nie zawiera GTM. Naprawa: dodaj https://www.googletagmanager.com do script-src.

Refused to apply inline style because it violates the following CSP directive: "style-src 'self'"

Przyczyna: GTM, Google Maps i reCAPTCHA wstrzykują style inline. Naprawa: dodaj 'unsafe-inline' do style-src lub użyj nonce, jeśli kontrolujesz wstrzykiwany kod. Hashe rzadko działają, bo style są generowane dynamicznie.

Refused to execute inline script because it violates the following CSP directive

Przyczyna: snippet GTM lub dataLayer push jest inline bez nonce. Opcje naprawy:

  • Dodaj 'unsafe-inline' do script-src (osłabia CSP).
  • Dodaj nonce-xyz do każdego inline’owego <script> ORAZ do script-src. W połączeniu z 'strict-dynamic' nonce pozwala GTM ładować kolejne tagi bez wypisywania wszystkich domen.

Refused to connect to 'https://region1.google-analytics.com/g/collect'

Przyczyna: GA4 wysyła hity na regionalne endpointy (region1, region2, …). Naprawa: użyj wildcardu: https://*.google-analytics.com w connect-src.

Refused to load the image 'https://stats.g.doubleclick.net/...'

Przyczyna: GA4 używa DoubleClick do pomiarów cross-site. Naprawa: dodaj https://stats.g.doubleclick.net do img-src i connect-src.

Refused to create a worker from 'blob:https://...'

Przyczyna: Google Maps używa Web Workers tworzonych z blob URL. Naprawa: dodaj worker-src 'self' blob: (oraz blob: do script-src, jeśli korzystasz ze starszego CSP Level 2 bez worker-src).

Refused to frame 'https://www.google.com/maps/embed'

Przyczyna: Maps Embed używa frame-src, nie img-src. Naprawa: dodaj https://www.google.com do frame-src.

reCAPTCHA pokazuje “Cannot contact reCAPTCHA”

Przyczyna: zwykle connect-src blokuje endpoint challenge’a. Naprawa: dodaj https://www.google.com/recaptcha/ do script-src i frame-src, oraz https://www.gstatic.com do script-src.

Raporty zalewają report-uri po wdrożeniu

Przyczyna: rozszerzenia przeglądarki (menedżery haseł, blokery reklam) wstrzykują skrypty i generują naruszenia CSP, z którymi nic nie zrobisz. Naprawa: filtruj raporty, w których blocked-uri zaczyna się od chrome-extension://, moz-extension:// lub safari-web-extension://, zanim uruchomią alert.

Jak bezpiecznie wdrożyć CSP

Wdrożenie strict CSP na działającej witrynie zawsze coś zepsuje — gwarantowane. Trzymaj się tej kolejności:

  1. Przeanalizuj obecne ładowanie — otwórz DevTools → Network → filtruj po domenie. Zbierz wszystkie third-party hosty, które faktycznie są używane. Pomocna może być też lista sposobów na sprawdzenie cookies i trackerów na stronie, żeby nie zapomnieć o domenach ładowanych dopiero po akceptacji consent.
  2. Wdrożenie w trybie Report-Only na co najmniej 7 dni. Produkcyjny ruch ujawni przypadki brzegowe, których nie widać na dev/staging (stare podstrony, szablony maili otwierane w przeglądarce, warianty A/B testów).
  3. Raporty skieruj do prawdziwego endpointu — użyj Report URI lub własnego loggera. Posortuj naruszenia według częstotliwości.
  4. Zaostrzaj dyrektywa po dyrektywie. Najpierw wymuszaj img-src (niskie ryzyko), potem style-src, connect-src, a na końcu script-src (największe ryzyko przestoju).
  5. Przełącz na enforce mode z tą samą polityką. Równolegle zostaw nagłówek Report-Only do testowania kolejnej, ostrzejszej iteracji.
  6. Rewiduj co kwartał — Google dokłada nowe domeny (np. analytics.google.com/g/collect, regionalne region1/2/3.google-analytics.com). Twoja polityka musi za tym nadążać.

Sprawdź swoje CSP w czasie rzeczywistym z UPER SEO Auditor

Jeśli chcesz ciągły monitoring Content-Security-Policy bez kopiowania nagłówków do zewnętrznych walidatorów, zainstaluj naszą wtyczkę Chrome UPER SEO Auditor. Zakładka Security:

  • Rozbija Twoje CSP na poszczególne dyrektywy i pokazuje każde źródło osobno — od razu widać brakujące domeny.
  • Wyłapuje naruszenia CSP na żywo podczas przeglądania strony — każdy zablokowany zasób jest pogrupowany według dyrektywy, z flagą [Google] przy blokadach dotyczących usług Google (GTM, GA4, Maps, Fonts, reCAPTCHA).
  • Ostrzega, gdy CSP jest w trybie Report-Only — nie wdrożysz przez pomyłkę polityki, która niczego nie egzekwuje.
  • Sprawdza 10 nagłówków bezpieczeństwa poza CSP (HSTS, X-Frame-Options, Referrer-Policy, Permissions-Policy, COOP, COEP, CORP) z ważonym scorem 0-10.

Darmowa, działa lokalnie w przeglądarce — żadne dane nie opuszczają strony.

Podsumowanie

CSP dla usług Google wymaga balansu między bezpieczeństwem a funkcjonalnością:

  1. GTM wymaga kompromisów'unsafe-inline' jest często konieczny, chyba że zaadoptujesz nonce + 'strict-dynamic'
  2. Server-side GTM znacząco upraszcza CSP i eliminuje większość zewnętrznych domen skryptowych
  3. Nonce + strict-dynamic to aktualna dobra praktyka dla script-src
  4. Consent Mode v2 wymaga otwartego connect-src do Google przed wyrażeniem zgody
  5. Testuj w Report-Only przez minimum tydzień przed włączeniem wymuszania
  6. Monitoruj naruszenia stale — domeny Google z czasem się zmieniają

Dobrze skonfigurowana polityka CSP chroni użytkowników przed XSS, nie blokując funkcjonalności strony.

Najczęściej zadawane pytania o CSP dla usług Google

Dlaczego Google Tag Manager wymaga unsafe-inline?

GTM wstrzykuje inline'owe skrypty dla kontenera i tagów Custom HTML. Bez unsafe-inline (lub pasującego nonce) przeglądarka blokuje te wstrzyknięcia i GTM przestaje odpalać tagi. Nowoczesne rozwiązanie to nonce generowany per-request dodany do snippetu GTM oraz dyrektywa strict-dynamic, która pozwala GTM przenieść zaufanie na ładowane przez niego tagi bez wypisywania każdej domeny.

Czy można bezpiecznie używać unsafe-eval z GTM?

unsafe-eval jest potrzebny tylko, gdy Twój kontener GTM ma tagi Custom HTML lub Custom JavaScript wywołujące eval() lub new Function(). Większość tagów GA4 i Ads tego nie robi. Przejrzyj kontener w trybie Preview, usuń stare Custom HTML tam, gdzie to możliwe, a następnie wyrzuć unsafe-eval z polityki — znacząco osłabia on ochronę przed XSS.

Jak naprawić błąd "Refused to load googletagmanager.com"?

Dodaj https://www.googletagmanager.com do script-src. Jeśli dodatkowo ładujesz GA4 przez gtag.js w GTM, dodaj https://www.google-analytics.com do img-src i connect-src, a także https://*.google-analytics.com, jeśli pojawiają się błędy dla endpointów regionalnych.

Czy Google Consent Mode v2 wymaga specjalnych zmian w CSP?

Consent Mode v2 używa tych samych domen Google co GTM i GA4, więc jeśli tamte działają — Consent Mode też. Kluczowa różnica to fakt, że connect-src musi być otwarty do endpointów Google Analytics od pierwszego wejścia — Consent Mode wysyła pingi do Google w stanie "denied" jeszcze przed akceptacją cookies. Dodatkowo dodaj do whitelisty swojego dostawcę CMP (Cookiebot, Iubenda, OneTrust itp.).

Czy server-side GTM eliminuje potrzebę unsafe-inline?

Tak, i to jedna z jego największych zalet. Z server-side tagging na Twojej stronie ładuje się tylko mały first-party loader (często jeden skrypt z Twojej subdomeny), a ciężka logika tagów działa w kontenerze server-side. script-src można zredukować do self plus subdomena tagująca, z nonce — bez unsafe-inline, bez zewnętrznych domen Google w przeglądarce.

Jak testować CSP bez ryzyka dla produkcji?

Użyj nagłówka Content-Security-Policy-Report-Only. Przeglądarka przetwarza politykę i wysyła raporty naruszeń na report-uri, ale niczego nie blokuje. Uruchom tryb Report-Only przez co najmniej 7 dni na realnym ruchu, przejrzyj raporty, dociśnij politykę, a dopiero potem przełącz na wymuszający nagłówek Content-Security-Policy.

Jaka jest różnica między nonce a hash w CSP?

Nonce to losowa wartość generowana per-request i dołączana do każdego zaufanego inline'owego skryptu; przeglądarka uruchamia tylko te skrypty, których nonce pasuje do wartości w nagłówku CSP. Hash to fingerprint SHA-256 zawartości skryptu — przydatny, gdy skrypt się nie zmienia, ale kruchy, bo każda edycja go łamie. Nonce stosuj dla dynamicznej zawartości (GTM, React SSR), hashe dla statycznych snippetów inline.

Dlaczego zapytania do region1.google-analytics.com są blokowane?

GA4 wysyła hity kolekcjonera na regionalne endpointy (region1, region2, region3.google-analytics.com) zależnie od lokalizacji użytkownika. Jeśli dodałeś do whitelisty tylko www.google-analytics.com, ruch regionalny jest blokowany. Użyj wildcardu: https://*.google-analytics.com w connect-src.

Ź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