Content-Security-Policy (CSP) is an HTTP header that protects your site against XSS attacks and other threats. However, an overly restrictive CSP can block Google services like GTM, Analytics, or Maps. This guide shows how to configure CSP so all necessary services work.

TL;DR — ready-to-copy CSP for a typical site

If you just need a CSP header that works with GTM, GA4, Google Maps, Fonts, reCAPTCHA and YouTube, start with this and adjust:

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

Replace RANDOM with a per-request nonce generated server-side. Start in Content-Security-Policy-Report-Only mode to see what your real site actually loads — only then switch to enforcement.

What is Content-Security-Policy?

CSP defines allowed sources for different types of resources (scripts, styles, images, fonts). The browser blocks everything that is not explicitly allowed.

Basic Syntax

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

Main Directives

DirectiveControls
default-srcDefault source for all types
script-srcJavaScript scripts
style-srcCSS styles
img-srcImages
font-srcFonts
connect-srcXHR, fetch, WebSocket
frame-srcIframes
object-srcPlugins (Flash, Java)

Source Values

ValueMeaning
'self'Same domain
'none'Block everything
'unsafe-inline'Allow inline (dangerous!)
'unsafe-eval'Allow eval() (dangerous!)
'nonce-xyz'Only scripts with this nonce
'strict-dynamic'Trust scripts loaded by trusted ones
https:All HTTPS sources
*.example.comSubdomain

CSP for Google Tag Manager

GTM requires many domains and functionalities. Configuration depends on mode (client-side vs server-side).

GTM Client-Side - Minimal Configuration

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 Requires unsafe-inline and unsafe-eval

GTM dynamically injects scripts, which requires 'unsafe-inline'. Custom HTML tags may use eval(). This significantly weakens CSP.

Solution 1: Nonce for GTM

<!-- Generate nonce server-side -->
<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;

Solution 2: Server-Side GTM

Server-side GTM significantly simplifies CSP:

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

CSP for Google Analytics 4

GA4 with 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 with GTM

If you load GA4 through GTM, you need a combination of the above. See also dataLayer best practices — a well-designed dataLayer lets you reduce the number of Custom HTML tags, which directly simplifies your CSP policy.

CSP for 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 for Google Fonts

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

Alternative: Self-hosting Fonts

Hosting fonts locally eliminates external dependencies:

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

CSP for 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 for 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;

Gotcha 1: youtube-nocookie.com alone isn’t enough

Many sites use youtube-nocookie.com for privacy reasons — and whitelist only that domain in frame-src. That breaks the embed in several scenarios:

  • Related videos overlay at the end of a clip redirects to youtube.com.
  • Channel links and logo in the player bar open youtube.com.
  • Some player chrome assets are still served from youtube.com regardless of which domain you embed.

Fix: always whitelist both https://www.youtube-nocookie.com and https://www.youtube.com in frame-src, even if your <iframe src> points only to the no-cookie version.

Gotcha 2: thumbnail domains for lite embeds

If you use a “facade” pattern (click-to-play thumbnail instead of a full iframe) — popular for performance — the thumbnail image loads from a different domain than the player:

  • https://i.ytimg.com — default thumbnail host (vi/VIDEO_ID/maxresdefault.jpg).
  • https://img.youtube.com — legacy alias, still used by some libraries.

Both need to be in img-src, otherwise the preview silently fails and users see a blank placeholder.

Gotcha 3: Cross-Origin-Opener-Policy breaking YouTube

If you harden your site with Cross-Origin-Opener-Policy: same-origin, the YouTube iframe loses access to popups it opens (sign-in, sharing dialog, full-screen). The player appears to work, but certain interactions fail silently with no CSP error in the console. Fix: switch to Cross-Origin-Opener-Policy: same-origin-allow-popups.

For a full breakdown of COOP, COEP, CORP, crossOriginIsolated, and how to unlock SharedArrayBuffer without breaking third-party integrations, see the dedicated guide: COOP, COEP, CORP — cross-origin isolation explained.

Production-ready example

Here is the exact snippet we use on uper.pl, combining GTM + GA4 + YouTube no-cookie embeds in a single header:

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

Paired with Cross-Origin-Opener-Policy: same-origin-allow-popups and X-Frame-Options: SAMEORIGIN, this combination passes Mozilla Observatory with an A+ and still lets YouTube embeds work end-to-end.

CSP for 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://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 is mandatory for EU traffic since March 2024. It uses the same domains as GTM + GA4, but introduces two wrinkles you need to account for:

  1. Cookiebot, Iubenda, OneTrust and other CMPs each load from their own domain — whitelist whichever one you use.
  2. Consent signals are sent before consent is granted (in denied state), so connect-src to Google must be open from the first pageview, not after opt-in.
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;

See the Consent Mode v2 implementation guide and how to verify it’s working.

CSP for Google Identity Services (GIS) / One Tap

Google Identity Services replaced the old gapi.auth2 library. Sign-in with Google, One Tap, and the FedCM API all use these domains:

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;

Common gotcha: FedCM requires frame-ancestors on Google’s side, not yours. If One Tap silently fails to render, check accounts.google.com in Network tab — a 403 usually means your site is missing from the Google Cloud OAuth client’s authorized origins, not a CSP issue.

CSP for Firebase (Auth, Firestore, Hosting)

Firebase spans several Google properties. Each product needs its own set of entries:

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 specifically uses signInWithRedirect which opens __/auth/handler on your Firebase Hosting subdomain — make sure to allow it in frame-src.

Complete Configuration for a Typical Site

Site with GTM, GA4, Maps, and 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';

Implementing 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; ..."
        }
      ]
    }
  ]
}

Testing CSP

1. Report-Only Mode

Start with reporting mode, which doesn’t block but only logs:

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

2. Browser DevTools

Console will show CSP errors:

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/ - Google’s tool for CSP analysis.

4. Observatory by Mozilla

https://observatory.mozilla.org/ - comprehensive security audit.

CSP Violation Reporting

Reporting Endpoint

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

Receiving Reports (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. Start Restrictively

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

Add sources only when needed.

2. Avoid unsafe-inline and unsafe-eval

If possible, use nonce or hash instead of 'unsafe-inline':

<script nonce="random123">
  // Your code
</script>
Content-Security-Policy: script-src 'nonce-random123';

3. Use strict-dynamic

'strict-dynamic' allows trusted scripts to load subsequent ones:

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

4. Consider Server-Side GTM

Server-side tagging drastically simplifies CSP and increases security.

5. Update Regularly

Google may change domains. Monitor CSP reports and update the policy.

Troubleshooting — common CSP errors and fixes

Below are the exact console messages you’ll see in Chrome / Firefox DevTools, and what to add to your policy to fix each one.

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

Cause: script-src doesn’t include GTM. Fix: add https://www.googletagmanager.com to script-src.

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

Cause: GTM, Google Maps, and reCAPTCHA all inject inline styles. Fix: add 'unsafe-inline' to style-src, or use a nonce if you control the injected markup. Hashes rarely work here because styles are generated dynamically.

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

Cause: your GTM snippet or dataLayer push is inline without a nonce. Fix options:

  • Add 'unsafe-inline' to script-src (weakens CSP).
  • Add nonce-xyz to every inline <script> tag AND to script-src. Paired with 'strict-dynamic', nonce lets GTM load downstream tags without listing every domain.

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

Cause: GA4 sends hits to region-specific endpoints (region1, region2, …). Fix: use a wildcard: https://*.google-analytics.com in connect-src.

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

Cause: GA4 uses DoubleClick for cross-site measurement. Fix: add https://stats.g.doubleclick.net to both img-src and connect-src.

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

Cause: Google Maps uses Web Workers created from blob URLs. Fix: add worker-src 'self' blob: (and blob: to script-src if you’re on an older CSP Level 2 without worker-src).

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

Cause: Maps Embed uses frame-src, not img-src. Fix: add https://www.google.com to frame-src.

reCAPTCHA shows “Cannot contact reCAPTCHA”

Cause: usually connect-src is blocking the challenge endpoint. Fix: add https://www.google.com/recaptcha/ to both script-src and frame-src, and https://www.gstatic.com to script-src.

Reports flood to report-uri after deploy

Cause: browser extensions (password managers, ad blockers) inject scripts and trigger CSP violations you can’t do anything about. Fix: filter reports where blocked-uri starts with chrome-extension://, moz-extension://, or safari-web-extension:// before alerting.

How to roll out CSP safely

Deploying a strict CSP on an existing site will break something — guaranteed. Follow this sequence:

  1. Audit current load — open DevTools → Network → filter by domain. Collect every third-party host actually used. Our guide to checking cookies and trackers on a page is useful here so you don’t miss domains that only load after consent is granted.
  2. Deploy in Report-Only mode for at least 7 days. Production traffic will reveal edge cases dev/staging misses (legacy pages, email templates opened in-browser, A/B test variants).
  3. Route reports to a real endpoint — use Report URI or your own logger. Triage violations by frequency.
  4. Tighten directive by directive. Enforce img-src first (low risk), then style-src, connect-src, and finally script-src (highest breakage risk).
  5. Switch to enforce mode with the same policy. Keep the Report-Only header in parallel when testing a stricter next iteration.
  6. Revisit quarterly — Google adds domains (e.g. analytics.google.com/g/collect, regional region1/2/3.google-analytics.com). Your policy needs to follow.

Check your CSP in real time with UPER SEO Auditor

If you want an ongoing sanity check on your Content-Security-Policy — without copy-pasting headers into online validators — install our Chrome extension UPER SEO Auditor. The Security tab:

  • Parses your CSP into individual directives and shows every source side-by-side, so you can spot missing domains at a glance.
  • Captures live CSP violations as you browse — every blocked resource is grouped by violated directive, with a [Google] flag on blocks affecting Google services (GTM, GA4, Maps, Fonts, reCAPTCHA).
  • Warns if CSP is in Report-Only mode so you don’t deploy a weakened policy to production by mistake.
  • Checks 10 security headers beyond CSP (HSTS, X-Frame-Options, Referrer-Policy, Permissions-Policy, COOP, COEP, CORP) with a weighted 0-10 score.

It’s free and runs entirely in your browser — no data leaves the page.

Summary

CSP for Google Services requires balancing security and functionality:

  1. GTM requires compromises'unsafe-inline' is often necessary unless you adopt nonce + 'strict-dynamic'
  2. Server-side GTM significantly simplifies CSP and removes most third-party script domains
  3. Nonce + strict-dynamic is the current best practice for script-src
  4. Consent Mode v2 needs connect-src open to Google before consent is granted
  5. Test in Report-Only for at least a week before enforcing
  6. Monitor violations continuously — Google domains drift over time

A well-configured CSP protects users against XSS without blocking site functionality.

Frequently Asked Questions about CSP for Google Services

Why does Google Tag Manager require unsafe-inline?

GTM injects inline scripts for its container and Custom HTML tags. Without unsafe-inline (or a matching nonce), the browser blocks those injections and GTM stops firing tags. The modern fix is to add a per-request nonce to the GTM snippet and use strict-dynamic, which lets GTM propagate trust to tags it loads without listing every domain.

Is it safe to use unsafe-eval with GTM?

unsafe-eval is only needed if your GTM container uses Custom HTML or Custom JavaScript tags that call eval() or new Function(). Most GA4 and Ads tags do not. Audit your container in Preview mode, remove legacy Custom HTML where possible, and drop unsafe-eval from your policy — it materially weakens XSS protection.

How do I fix "Refused to load googletagmanager.com" errors?

Add https://www.googletagmanager.com to script-src. If you also load GA4 via gtag.js through GTM, add https://www.google-analytics.com to img-src and connect-src, and https://*.google-analytics.com if you see regional endpoint errors.

Does Google Consent Mode v2 need special CSP changes?

Consent Mode v2 uses the same Google domains as GTM and GA4, so if those work, Consent Mode works. The key change is that connect-src must be open to Google analytics endpoints from the first pageview — Consent Mode pings Google in "denied" state before the user accepts cookies. Additionally, whitelist your CMP provider (Cookiebot, Iubenda, OneTrust, etc.).

Can server-side GTM eliminate the need for unsafe-inline?

Yes, and that is one of its biggest advantages. With server-side tagging, only a small first-party loader runs on your site (often just one script from your own subdomain), and the heavy tag logic runs on your server container. Your script-src can be reduced to self plus your tagging subdomain, with a nonce — no unsafe-inline, no third-party Google domains in the browser.

How do I test CSP without breaking my production site?

Use the Content-Security-Policy-Report-Only header. The browser processes your policy and sends violation reports to a report-uri endpoint, but never blocks anything. Run it in Report-Only for at least 7 days against real traffic, review reports, tighten the policy, then switch to the enforcing Content-Security-Policy header.

What is the difference between nonce and hash in CSP?

A nonce is a random value generated per request and attached to each trusted inline script tag; the browser only executes scripts whose nonce matches the one in the CSP header. A hash is a SHA-256 fingerprint of the script contents — useful when the script never changes, but brittle because any edit breaks it. Use nonces for dynamic content (GTM, React SSR) and hashes for static inline snippets.

Why are region1.google-analytics.com requests blocked?

GA4 routes collection hits to regional endpoints (region1, region2, region3.google-analytics.com) depending on user location. If you only whitelisted www.google-analytics.com, regional traffic is blocked. Use a wildcard: https://*.google-analytics.com in connect-src.

Sources

  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