{"id":985,"date":"2026-05-13T10:02:31","date_gmt":"2026-05-13T08:02:31","guid":{"rendered":"https:\/\/www.sabatka.net\/en\/?p=985"},"modified":"2026-05-18T08:01:33","modified_gmt":"2026-05-18T06:01:33","slug":"gtm-vs-csp-tracking-and-security","status":"publish","type":"post","link":"https:\/\/www.sabatka.net\/en\/gtm-vs-csp-tracking-and-security\/","title":{"rendered":"GTM vs. CSP \u2014 How to Make Tracking and Security Coexist"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">A developer deploys Content Security Policy on the company website \u2014 and tracking silently breaks. Or the reverse: an analyst adds a tag to Google Tag Manager (GTM) and pushes through a CSP exception \u2014 measurement works, but the site is wide open.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">GTM is inherently a script injector \u2014 it loads third-party scripts into the page. Content Security Policy (CSP) is a script blocker \u2014 it blocks everything not on the allowlist. Deploy both without coordination and you\u2019ll lose either your data or your security. This isn\u2019t about HTTP headers. It\u2019s about whether you have data for decisions \u2014 and whether that data is safe.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In the <a href=\"https:\/\/www.sabatka.net\/en\/gtm-security-risks\/\">previous article<\/a>, I showed what can go wrong when GTM has no oversight \u2014 from innocent mistakes to documented attacks on e-commerce stores. Now let\u2019s look at the technical solution: how to configure CSP so that tracking works and the site stays protected.<\/p>\n\n\n\n<div style=\"height:50px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button scroll_to_subscribe\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/www.sabatka.net\/en\/kontakt\/\">Contact me<\/a><\/div>\n\n\n\n<div class=\"wp-block-button linkedinShare\"><a class=\"wp-block-button__link wp-element-button\">Share on LinkedIN<\/a><\/div>\n<\/div>\n\n\n\n<div style=\"height:50px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">What Is Content Security Policy<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">CSP is an HTTP header that tells the browser: \u201cYou may only run scripts from these sources.\u201d Nothing more, nothing less.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: \n  script-src 'self' https:\/\/*.googletagmanager.com;\n  connect-src 'self' https:\/\/*.google-analytics.com;\n  img-src 'self' https:\/\/*.google-analytics.com;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For analytics, these directives matter most:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><code>script-src<\/code><\/strong> \u2014 which scripts may execute (the critical one)<\/li>\n\n\n\n<li><strong><code>connect-src<\/code><\/strong> \u2014 where data can be sent (XHR, fetch, beacons)<\/li>\n\n\n\n<li><strong><code>img-src<\/code><\/strong> \u2014 where images (and tracking pixels) can load from<\/li>\n\n\n\n<li><strong><code>frame-src<\/code><\/strong> \u2014 allowed iframes<\/li>\n\n\n\n<li><strong><code>style-src<\/code><\/strong> \u2014 stylesheets (relevant for GTM preview mode)<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The browser doesn\u2019t distinguish between a \u201cfriendly\u201d GA4 script and malicious JavaScript.<\/strong> If a script isn\u2019t on the allowlist, the browser blocks it. Silently, with no warning for the user \u2014 and often no warning for you.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<div style=\"height:50px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Why GTM Is a Nightmare for CSP<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">GTM operates on two levels \u2014 both problematic for CSP.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Level one:<\/strong> the container snippet. An inline script in the page\u2019s <code><\/code>. Needs permission in <code>script-src<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Level two:<\/strong> tags loaded by the container. GTM fetches its configuration and dynamically injects additional scripts \u2014 GA4, Meta Pixel, Hotjar, LinkedIn Insight Tag\u2026 Each vendor needs its own domains in CSP.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Every new tag in GTM = a potential new domain in CSP.<\/strong> A marketer adds a tag in 2 minutes. Changing the CSP header requires a Jira ticket, code review, deploy, and waiting.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Real consequences of poor configuration:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Silent data loss<\/strong> \u2014 tracking stops working, but nobody notices. The data gap can\u2019t be backfilled.<\/li>\n\n\n\n<li><strong>Broken campaigns<\/strong> \u2014 conversion pixels don\u2019t fire, campaigns can\u2019t be evaluated.<\/li>\n\n\n\n<li><strong>Broken debugging<\/strong> \u2014 GTM preview mode requires directives for <code>tagmanager.google.com<\/code>; without them, debugging doesn\u2019t work.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">I\u2019ll show how to solve this shortly. But first, it\u2019s important to understand why a poorly configured CSP can be worse than no CSP at all.<\/p>\n\n\n\n<div style=\"height:50px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">When the Cure Kills: GTM as a Security Risk<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Security firm <a href=\"https:\/\/raxis.com\/blog\/bypassing-waf-and-csp-with-google-tag-manager\/\" target=\"_blank\" rel=\"noopener\">Raxis demonstrated an attack<\/a> using a GTM container to bypass both WAF and CSP:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Attacker finds an XSS vulnerability on the site<\/li>\n\n\n\n<li>Injects a reference to their own GTM container with a malicious Custom HTML tag<\/li>\n\n\n\n<li>The payload lives on <code>googletagmanager.com<\/code> \u2014 WAF doesn\u2019t flag it, CSP allows it<\/li>\n\n\n\n<li>Code executes in the page context \u2014 access to cookies, session tokens, forms<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Google acknowledged it as an \u201chonorable mention\u201d in the Bug Bounty program. More documented GTM attacks \u2014 including payment data theft from hundreds of e-commerce sites \u2014 in the <a href=\"https:\/\/www.sabatka.net\/en\/gtm-security-risks\/\">previous article<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Three conditions must be met simultaneously:<\/strong> (1) an XSS vulnerability on the site, (2) CSP without nonce for GTM scripts, (3) <code>unsafe-inline<\/code> or another weak CSP directive. Meet all three? You have a problem.<\/p>\n\n\n\n<div style=\"height:20px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-794e3cfa wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:33.33%\">\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"1024\" height=\"1024\" data-src=\"https:\/\/www.sabatka.net\/wp-content\/uploads\/2026\/04\/null-did-you-know.png\" alt=\"\" class=\"wp-image-936 lazyload\" data-srcset=\"https:\/\/www.sabatka.net\/wp-content\/uploads\/2026\/04\/null-did-you-know.png 1024w, https:\/\/www.sabatka.net\/wp-content\/uploads\/2026\/04\/null-did-you-know-300x300.png 300w, https:\/\/www.sabatka.net\/wp-content\/uploads\/2026\/04\/null-did-you-know-150x150.png 150w, https:\/\/www.sabatka.net\/wp-content\/uploads\/2026\/04\/null-did-you-know-768x768.png 768w\" data-sizes=\"(max-width: 1024px) 100vw, 1024px\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 1024px; --smush-placeholder-aspect-ratio: 1024\/1024;\" \/><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:66.66%\">\n<h3 class=\"wp-block-heading\"><strong>Lab note<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">It\u2019s the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Perverse_incentive#The_original_cobra_effect\" target=\"_blank\" rel=\"noopener\">cobra effect<\/a> \u2014 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 \u201cfor security\u201d but weakened for GTM with <code>unsafe-inline<\/code> works the same way \u2014 it creates a false sense of security and opens the door to attackers.<\/p>\n<\/div>\n<\/div>\n\n\n\n<div style=\"height:20px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>A poorly configured CSP + GTM is worse than no CSP.<\/strong> Not just psychologically \u2014 technically too. CSP with <code>unsafe-inline<\/code> explicitly tells the browser \u201crun any inline script.\u201d The result is the same as having no CSP, but security audits see \u201cCSP: yes\u201d and move on. The team stops investing in other protections because \u201cwe have CSP.\u201d And the Raxis-style attack shows exactly how to exploit that false confidence.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Nonce + strict-dynamic: The Secure Path<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In my view, the ideal approach \u2014 when feasible.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Nonce<\/strong> is a random token that the server generates for each HTTP response:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Server generates a unique nonce (e.g., <code>a8f5e2b1<\/code>)<\/li>\n\n\n\n<li>Nonce goes into the CSP header: <code>script-src 'nonce-a8f5e2b1' 'strict-dynamic'<\/code><\/li>\n\n\n\n<li>Same nonce is added to the GTM snippet: <code><script nonce=\"a8f5e2b1\"><\/code><\/li>\n\n\n\n<li>GTM automatically propagates the nonce to built-in and template tags<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: script-src 'nonce-a8f5e2b1' 'strict-dynamic'\n\n<script nonce=\"a8f5e2b1\">\n  (function(w,d,s,l,i){...})(window,document,'script','dataLayer','GTM-XXXXX');\n<\/script><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>strict-dynamic<\/code> is a CSP3 standard (<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Reference\/Headers\/Content-Security-Policy\/script-src\" target=\"_blank\" rel=\"noopener\">defined in the MDN spec<\/a>). It says: \u201ctrust scripts loaded by an already-approved script.\u201d No need to list vendor domains \u2014 the GTM container is approved via nonce and everything it loads inherits trust.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Google\u2019s official documentation doesn\u2019t explicitly mention <code>strict-dynamic<\/code>. It is, however, a well-established and working approach recommended by the community.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Watch Out for Custom HTML Tags<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">GTM propagates the nonce automatically to <strong>built-in tags and Community Template Gallery templates<\/strong>. It does <strong>not<\/strong> propagate nonce to Custom HTML tags.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you have a Custom HTML tag, you must:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Manually add the <code>{{nonce}}<\/code> variable to every <code>script<\/code> tag in Custom HTML<\/li>\n\n\n\n<li>Handle a Chrome-specific workaround \u2014 Chrome masks the <code>nonce<\/code> attribute, so you need <code>data-nonce<\/code><\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Not impossible, but fragile and requires maintenance with every new Custom HTML tag.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Limitations of the Nonce Approach<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Doesn\u2019t work with full-page cache (each response needs a unique nonce)<\/li>\n\n\n\n<li>Requires server-side changes \u2014 analysts don\u2019t always have server access<\/li>\n\n\n\n<li>Edge cases with some SPA frameworks (Next.js \u2014 client-side components can\u2019t generate nonce server-side)<\/li>\n<\/ul>\n\n\n\n<div style=\"height:50px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Domain Allowlist: The Pragmatic Path<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Nonce requires dynamic token generation in application code on every request \u2014 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 <code><meta><\/code> tag. Both require server-side configuration (or a <code><meta><\/code> tag), but an allowlist is a static config, not an application code change:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n  script-src 'self' https:\/\/*.googletagmanager.com;\n  connect-src 'self' https:\/\/*.google-analytics.com \n                     https:&#47;&#47;*.analytics.google.com;\n  img-src 'self' https:\/\/*.google-analytics.com;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For each additional vendor (Meta, LinkedIn, Hotjar\u2026) you add their domains.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Does it work? Yes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Is it elegant? No.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Vendors change domains without warning \u2192 CSP breaks and you find out from missing data<\/li>\n\n\n\n<li>Geo-specific domains (<code>google.fr<\/code> vs <code>google.com<\/code>) depend on user location<\/li>\n\n\n\n<li>Every new GTM tag = Jira ticket, deploy, waiting<\/li>\n\n\n\n<li>Ongoing maintenance \u2014 \u201cset and forget\u201d doesn\u2019t work<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">For sites with full-page cache or where there\u2019s no capacity to modify the application layer, this may be the only realistic option.<\/p>\n\n\n\n<div style=\"height:50px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Templates vs. Custom HTML: Where It All Breaks Down<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Custom HTML tags<\/strong> and <strong>Custom JavaScript variables<\/strong> are problematic from a CSP perspective. Custom JS variables require <code>unsafe-eval<\/code> in <code>script-src<\/code> \u2014 without it, they return <code>undefined<\/code> (<a href=\"https:\/\/developers.google.com\/tag-platform\/security\/guides\/csp\" target=\"_blank\" rel=\"noopener\">Google documentation<\/a>). Custom HTML tags can work with nonce, but require manual configuration and browser workarounds.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>unsafe-eval<\/code> is even riskier than <code>unsafe-inline<\/code> \u2014 it allows <code>eval()<\/code>, <code>new Function()<\/code>, and other dynamic code execution. Exactly what CSP is designed to prevent.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>GTM templates<\/strong> (Community Template Gallery or custom) don\u2019t have this problem. They run in GTM\u2019s sandboxed environment, function as methods inside <code>gtm.js<\/code>, and don\u2019t need <code>unsafe-eval<\/code> or <code>unsafe-inline<\/code>. The GTM API (<code>sendPixel<\/code>, <code>injectScript<\/code>, <code>setCookie<\/code>\u2026) is designed to work with CSP.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Google explicitly recommends migrating from Custom HTML to templates<\/strong> as the secure alternative.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you have strict CSP and Custom HTML tags, you\u2019re choosing between three options:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Rewrite as templates<\/strong> \u2014 most secure, but extra work. Not everything can be converted.<\/li>\n\n\n\n<li><strong>Manually configure nonce<\/strong> for Custom HTML tags + browser workarounds \u2014 works, but fragile<\/li>\n\n\n\n<li><strong>Allow <code>unsafe-eval<\/code> \/ <code>unsafe-inline<\/code><\/strong> as a fallback \u2014 easiest, but undermines the purpose of CSP<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Which path to choose depends on how many Custom HTML tags you have and how strict your security requirements are.<\/p>\n\n\n\n<div style=\"height:50px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Where Does Server-Side GTM Fit In?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">sGTM can slightly simplify CSP, but it certainly won\u2019t solve it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>What sGTM actually simplifies:<\/strong> loading the GTM container. The <code>gtm.js<\/code> file is served from your subdomain via sGTM \u2014 the content is identical to what you\u2019d download from Google\u2019s domain, but in <code>script-src<\/code> you only need your own domain instead of <code>googletagmanager.com<\/code>. With Google Tag Gateway, a <code>self<\/code> directive may be enough.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>What sGTM doesn\u2019t solve:<\/strong> 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 \u2014 and therefore in CSP.<\/p>\n\n\n\n<div style=\"height:50px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Report-Only and Monitoring: Before Going Live<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Advice for anyone starting with CSP: <strong>don\u2019t enable enforcement right away.<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Start with <code>Content-Security-Policy-Report-Only<\/code>. It works like a live CSP, but instead of blocking, it only collects violation reports. Let it run for at least 7 days.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy-Report-Only:\n  script-src 'nonce-{token}' 'strict-dynamic';\n  report-uri \/csp-report-endpoint;<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Where to Send Violation Reports<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A dedicated endpoint logging to BigQuery or a logging tool<\/li>\n\n\n\n<li>A JavaScript listener on the <code>securitypolicyviolation<\/code> event \u2014 you see violations directly in the browser<\/li>\n\n\n\n<li>GTM Tag Assistant \u2014 can display sources blocked by CSP<\/li>\n<\/ul>\n\n\n\n<div style=\"height:20px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Preview Mode \u2014 An Overlooked Problem<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">GTM debug mode requires its own directives. Without them, debugging won\u2019t work:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>script-src: https:\/\/googletagmanager.com https:\/\/tagmanager.google.com\nstyle-src:  https:\/\/tagmanager.google.com https:\/\/fonts.googleapis.com\nimg-src:    https:\/\/ssl.gstatic.com https:\/\/www.gstatic.com\nfont-src:   https:\/\/fonts.gstatic.com data:<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Forgetting this means your debug mode either won\u2019t work at all or debugging will be painful.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Process Matters More Than Configuration<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<div style=\"height:50px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusions<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>CSP and GTM aren\u2019t enemies. But they require coordination between security and analytics teams.<\/li>\n\n\n\n<li>Practical step: open the browser console on your website. Look for CSP error messages (red errors). If they\u2019re there, you know where to start. If not \u2014 check whether you even have CSP.<\/li>\n\n\n\n<li>If you haven\u2019t read the <a href=\"https:\/\/www.notion.so\/en\/gtm-security-risks\" target=\"_blank\" rel=\"noopener\">first part about GTM security risks<\/a>, start there \u2014 it shows what can happen when GTM has no oversight.<\/li>\n<\/ol>\n\n\n\n<div style=\"height:50px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button scroll_to_subscribe\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/www.sabatka.net\/en\/kontakt\/\">Contact me<\/a><\/div>\n\n\n\n<div class=\"wp-block-button linkedinShare\"><a class=\"wp-block-button__link wp-element-button\">Share on LinkedIN<\/a><\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>A developer deploys Content Security Policy on the company website \u2014 and tracking silently breaks. Or the reverse: an analyst adds a tag to Google Tag Manager (GTM) and pushes through a CSP exception \u2014 measurement works, but the site is wide open. GTM is inherently a script injector \u2014 it loads third-party scripts into [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":990,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[31,9,10],"tags":[],"class_list":["post-985","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-security","category-digital-analytics","category-google-tag-manager-gtm"],"_links":{"self":[{"href":"https:\/\/www.sabatka.net\/en\/wp-json\/wp\/v2\/posts\/985","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sabatka.net\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sabatka.net\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sabatka.net\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sabatka.net\/en\/wp-json\/wp\/v2\/comments?post=985"}],"version-history":[{"count":29,"href":"https:\/\/www.sabatka.net\/en\/wp-json\/wp\/v2\/posts\/985\/revisions"}],"predecessor-version":[{"id":1116,"href":"https:\/\/www.sabatka.net\/en\/wp-json\/wp\/v2\/posts\/985\/revisions\/1116"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.sabatka.net\/en\/wp-json\/wp\/v2\/media\/990"}],"wp:attachment":[{"href":"https:\/\/www.sabatka.net\/en\/wp-json\/wp\/v2\/media?parent=985"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sabatka.net\/en\/wp-json\/wp\/v2\/categories?post=985"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sabatka.net\/en\/wp-json\/wp\/v2\/tags?post=985"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}