A developer deploys Content Security Policy on the company website — and tracking silently breaks. Or the reverse: an analyst adds a tag to Google Tag Manager (GTM) and pushes through a CSP exception — measurement works, but the site is wide open.
GTM is inherently a script injector — it loads third-party scripts into the page. Content Security Policy (CSP) is a script blocker — it blocks everything not on the allowlist. Deploy both without coordination and you’ll lose either your data or your security. This isn’t about HTTP headers. It’s about whether you have data for decisions — and whether that data is safe.
In the previous article, I showed what can go wrong when GTM has no oversight — from innocent mistakes to documented attacks on e-commerce stores. Now let’s look at the technical solution: how to configure CSP so that tracking works and the site stays protected.
What Is Content Security Policy
CSP is an HTTP header that tells the browser: “You may only run scripts from these sources.” Nothing more, nothing less.
Content-Security-Policy:
script-src 'self' https://*.googletagmanager.com;
connect-src 'self' https://*.google-analytics.com;
img-src 'self' https://*.google-analytics.com;
For analytics, these directives matter most:
script-src— which scripts may execute (the critical one)connect-src— where data can be sent (XHR, fetch, beacons)img-src— where images (and tracking pixels) can load fromframe-src— allowed iframesstyle-src— stylesheets (relevant for GTM preview mode)
The browser doesn’t distinguish between a “friendly” GA4 script and malicious JavaScript. If a script isn’t on the allowlist, the browser blocks it. Silently, with no warning for the user — and often no warning for you.
CSP protects against XSS attacks, data exfiltration, and malicious code injection. It does this well. The problem starts when a tag manager enters the equation.
Why GTM Is a Nightmare for CSP
GTM operates on two levels — both problematic for CSP.
Level one: the container snippet. An inline script in the page’s . Needs permission in script-src.
Level two: tags loaded by the container. GTM fetches its configuration and dynamically injects additional scripts — GA4, Meta Pixel, Hotjar, LinkedIn Insight Tag… Each vendor needs its own domains in CSP.
Every new tag in GTM = a potential new domain in CSP. A marketer adds a tag in 2 minutes. Changing the CSP header requires a Jira ticket, code review, deploy, and waiting.
Real consequences of poor configuration:
- Silent data loss — tracking stops working, but nobody notices. The data gap can’t be backfilled.
- Broken campaigns — conversion pixels don’t fire, campaigns can’t be evaluated.
- Broken debugging — GTM preview mode requires directives for
tagmanager.google.com; without them, debugging doesn’t work.
I’ll show how to solve this shortly. But first, it’s important to understand why a poorly configured CSP can be worse than no CSP at all.
When the Cure Kills: GTM as a Security Risk
Security firm Raxis demonstrated an attack using a GTM container to bypass both WAF and CSP:
- Attacker finds an XSS vulnerability on the site
- Injects a reference to their own GTM container with a malicious Custom HTML tag
- The payload lives on
googletagmanager.com— WAF doesn’t flag it, CSP allows it - Code executes in the page context — access to cookies, session tokens, forms
Google acknowledged it as an “honorable mention” in the Bug Bounty program. More documented GTM attacks — including payment data theft from hundreds of e-commerce sites — in the previous article.
Three conditions must be met simultaneously: (1) an XSS vulnerability on the site, (2) CSP without nonce for GTM scripts, (3) unsafe-inline or another weak CSP directive. Meet all three? You have a problem.

Lab note
It’s the cobra effect — the British colonial government in India offered a bounty for dead cobras. People started breeding them. When the government cancelled the bounty, breeders released the cobras and the population grew. The solution made the problem worse. CSP deployed “for security” but weakened for GTM with unsafe-inline works the same way — it creates a false sense of security and opens the door to attackers.
A poorly configured CSP + GTM is worse than no CSP. Not just psychologically — technically too. CSP with unsafe-inline explicitly tells the browser “run any inline script.” The result is the same as having no CSP, but security audits see “CSP: yes” and move on. The team stops investing in other protections because “we have CSP.” And the Raxis-style attack shows exactly how to exploit that false confidence.
Nonce + strict-dynamic: The Secure Path
In my view, the ideal approach — when feasible.
Nonce is a random token that the server generates for each HTTP response:
- Server generates a unique nonce (e.g.,
a8f5e2b1) - Nonce goes into the CSP header:
script-src 'nonce-a8f5e2b1' 'strict-dynamic' - Same nonce is added to the GTM snippet:
strict-dynamicis a CSP3 standard (defined in the MDN spec). It says: “trust scripts loaded by an already-approved script.” No need to list vendor domains — the GTM container is approved via nonce and everything it loads inherits trust.Google’s official documentation doesn’t explicitly mention
strict-dynamic. It is, however, a well-established and working approach recommended by the community.Watch Out for Custom HTML Tags
GTM propagates the nonce automatically to built-in tags and Community Template Gallery templates. It does not propagate nonce to Custom HTML tags.
If you have a Custom HTML tag, you must:
- Manually add the
{{nonce}}variable to everyscripttag in Custom HTML - Handle a Chrome-specific workaround — Chrome masks the
nonceattribute, so you needdata-nonce
Not impossible, but fragile and requires maintenance with every new Custom HTML tag.
Limitations of the Nonce Approach
- Doesn’t work with full-page cache (each response needs a unique nonce)
- Requires server-side changes — analysts don’t always have server access
- Edge cases with some SPA frameworks (Next.js — client-side components can’t generate nonce server-side)
Domain Allowlist: The Pragmatic Path
Nonce requires dynamic token generation in application code on every request — changes to middleware or the server framework. An allowlist is simpler: you set static values in the web server config (nginx, Apache), CDN, or via a
tag. Both require server-side configuration (or atag), but an allowlist is a static config, not an application code change:Content-Security-Policy: script-src 'self' https://*.googletagmanager.com; connect-src 'self' https://*.google-analytics.com https://*.analytics.google.com; img-src 'self' https://*.google-analytics.com;For each additional vendor (Meta, LinkedIn, Hotjar…) you add their domains.
Does it work? Yes.
Is it elegant? No.
- Vendors change domains without warning → CSP breaks and you find out from missing data
- Geo-specific domains (
google.frvsgoogle.com) depend on user location - Every new GTM tag = Jira ticket, deploy, waiting
- Ongoing maintenance — “set and forget” doesn’t work
For sites with full-page cache or where there’s no capacity to modify the application layer, this may be the only realistic option.
Templates vs. Custom HTML: Where It All Breaks Down
Custom HTML tags and Custom JavaScript variables are problematic from a CSP perspective. Custom JS variables require
unsafe-evalinscript-src— without it, they returnundefined(Google documentation). Custom HTML tags can work with nonce, but require manual configuration and browser workarounds.unsafe-evalis even riskier thanunsafe-inline— it allowseval(),new Function(), and other dynamic code execution. Exactly what CSP is designed to prevent.GTM templates (Community Template Gallery or custom) don’t have this problem. They run in GTM’s sandboxed environment, function as methods inside
gtm.js, and don’t needunsafe-evalorunsafe-inline. The GTM API (sendPixel,injectScript,setCookie…) is designed to work with CSP.Google explicitly recommends migrating from Custom HTML to templates as the secure alternative.
If you have strict CSP and Custom HTML tags, you’re choosing between three options:
- Rewrite as templates — most secure, but extra work. Not everything can be converted.
- Manually configure nonce for Custom HTML tags + browser workarounds — works, but fragile
- Allow
unsafe-eval/unsafe-inlineas a fallback — easiest, but undermines the purpose of CSP
Which path to choose depends on how many Custom HTML tags you have and how strict your security requirements are.
Where Does Server-Side GTM Fit In?
sGTM can slightly simplify CSP, but it certainly won’t solve it.
What sGTM actually simplifies: loading the GTM container. The
gtm.jsfile is served from your subdomain via sGTM — the content is identical to what you’d download from Google’s domain, but inscript-srcyou only need your own domain instead ofgoogletagmanager.com. With Google Tag Gateway, aselfdirective may be enough.What sGTM doesn’t solve: the web GTM container still loads scripts from third-party domains in most implementations. Some platforms require sending data both from the frontend and the server (e.g., Meta Pixel + Conversions API via sGTM). And some tools (MS Clarity, Hotjar) have no server-side version at all. Their scripts must stay on the page — and therefore in CSP.
Report-Only and Monitoring: Before Going Live
Advice for anyone starting with CSP: don’t enable enforcement right away.
Start with
Content-Security-Policy-Report-Only. It works like a live CSP, but instead of blocking, it only collects violation reports. Let it run for at least 7 days.Content-Security-Policy-Report-Only: script-src 'nonce-{token}' 'strict-dynamic'; report-uri /csp-report-endpoint;Where to Send Violation Reports
- A dedicated endpoint logging to BigQuery or a logging tool
- A JavaScript listener on the
securitypolicyviolationevent — you see violations directly in the browser - GTM Tag Assistant — can display sources blocked by CSP
Preview Mode — An Overlooked Problem
GTM debug mode requires its own directives. Without them, debugging won’t work:
script-src: https://googletagmanager.com https://tagmanager.google.com style-src: https://tagmanager.google.com https://fonts.googleapis.com img-src: https://ssl.gstatic.com https://www.gstatic.com font-src: https://fonts.gstatic.com data:Forgetting this means your debug mode either won’t work at all or debugging will be painful.
Process Matters More Than Configuration
The best CSP is useless if changes take a month. A fast workflow for CSP updates (ideally within a day) is just as important as the configuration itself. The analyst needs to know about CSP, the developer needs to know about tracking dependencies.
Conclusions
- CSP and GTM aren’t enemies. But they require coordination between security and analytics teams.
- Practical step: open the browser console on your website. Look for CSP error messages (red errors). If they’re there, you know where to start. If not — check whether you even have CSP.
- If you haven’t read the first part about GTM security risks, start there — it shows what can happen when GTM has no oversight.
- Manually add the
