The internet is a beautiful place. If you think chaos is beautiful, that is, because it is also a place where everyone and everything is hacked, abused, and manipulated for money, status or just the lolz. To prevent your precious Google Tag Manager implementation —and your entire site for that matter— from falling victim to malicious code taking over checkout funnels or secretly listening to form input from visitors it’s time to implement a Content Security Policy (CSP). At the end of this you should understand:
- What a CSP is and how it helps to protect your site’s visitors
- The security risk that Google Tag Manager poses to your site, and how using GTM with a Content Security Policy (CSP) can prevent your visitors from malicious code
- What a ‘nonce’ is, what a ‘hash’ is and how nonces and hashes can help you implement GTM and other scripts securely.
- How to take away any pain points from your developers when implementing scripts alongside a CSP.
To serve and protect
Google Tag Manager is a great way to serve a random collection of scripts and images (tags) that help you do all sorts of things like tracking purchase conversions for Facebook Ads, tracking funnels with Google Analytics or even send Slack messages when a user sees a 404 page. Your browser, the beacon of light trying to protect you from all the bad out there in the digital world, has no way to distinguish those ‘good’ scripts from the ‘bad’ scripts trying to steal your credit card information.
As site owners we can help the browser protect you from malicious code by telling it how to deal with code that connects to anything other than the current document being loaded. That ‘other’ thing could be your self-hosted
cats.gif image you’d like to show the world or the Google Analytics tracking code to see how many people have looked at your cat GIF. A Content Security Policy to deal with this highly advanced Cat GIF page could look something like this:
default-src 'self'; img-src https://www.google-analytics.com; script-src https://www.google-analytics.com;
You will find the CSP:
- In your browser’s network tab, under the headers for the request to load the page.
- In a meta tag in the actual HTML of the page
<meta http-equiv="Content-Security-Policy" content="" >
When we use the above CSP, we’ll find that it blocks everything except:
- Any resource loaded from the same domain
- Images (
<img src="" />) loaded from the domain
- Scripts loaded from
This works quite well if you want to do basic tracking with Google Analytics. You might know however, that GA can also send POST requests and use the browser’s beacon function to send information more reliably. To do that we’ll also have to add
That way also so-called XHR requests are allowed.
That’s still not enough to fully run Google Analytics though, because, as you might know, GA actually asks you to add a piece of code to your site:
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-000000-1"></script>
window.dataLayer = window.dataLayer || ;
gtag('js', new Date());
By default, using
default-src 'self' also means that nothing between
<script></script> tags is executed. This is a good thing because if malicious code was somehow injected into our page it wouldn’t run.
You will sometimes see the argument
'unsafe-inline' added to a CSP. For example
script-src 'unsafe-inline' https://www.google-analytics.com; will allow you to run the Google Analytics script above. It will also allow you to run code to start a crypto-miner on the visitors computer. In other words, though slightly better than nothing, it’s still bad.
How to implement Google Tag Manager (GTM) with a Content Security Policy (CSP)
As we could see in the example above, implementing a CSP and making sure all your tags are still firing is not as easy as adding
script-src https://www.google-analytics.com https://www.googletagmanager.com;. Even if you use the
unsafe-eval. This will allow any script to dynamically execute any other script and basically render your CSP useless. Instead there are other ways to execute inline scripts on the page.
Execute inline script with a CSP using a hash
One way to make sure that a third party script is safe to execute is when you know the origin is safe (for example it comes from a trusted content delivery network like Google’s or Cloudflare’s) and you are absolutely sure it was not modified on the way to your site. You can make sure of the latter by verifying the integrity of the script with a hash. Any modification, even adding a space, will alter the hash and render the script invalid.
This process, called SubResource Integrity (SRI) is great for commonly used scripts like jQuery, Font Awesome, or Bootstrap. For example if we want to add Font Awesome to our site, we can just add it with the integrity attribute and be done.
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous" />
As you can see the resource is also versioned (4.7.0), so we know that as long as we use the same version, we will have the same script and thus the same hash.
Execute inline script with a CSP using a nonce
Think about GTM and what it does for a moment. It allows us to create a custom set of tags and variables, adjust that as we go and load that on our site. By definition GTM will be a different script every time we hit the publish button, so using hashes is out of the question. We can however use a so-called ‘nonce’ a number-used-once. By adding this ‘nonce’ to our script tag and then add the same nonce to our CSP header we can tell the browser that the contents of that script tag are safe and can be executed.
It is good to know however that once you go nonce there’s no way back and the
'unsafe-inline' argument will be invalidated. That means you’ll have to be prepared for a discussion with your developer as they might be using inline scripts and will say that “your update made our site crash”.
Executing Custom HTML tags in GTM with a content security policy (CSP)
The Custom HTML tag is a little different from all the other tags in GTM. The standard ‘template’ tags —including custom templates— are in a sandbox environment. That basically means that GTM can compile them into the GTM script and load them into your site when GTM is loaded because they stick to a certain set of rules. Custom HTML however could be anything. It’s impossible for GTM to include it in the first load, and so it has to be injected into the page after GTM is loaded.
However, as we saw before, to inject Custom HTML into the page we need to do two things:
- Add the nonce value
- Turn on the
document.writecheckbox so we use a different way of injecting into the page
Number two is easy, but number one is a PITA because nonces behave differently in Chrome than in Safari, Firefox or other browsers (!). One place where you can notice this is that if you look in the rendered source in the developer tools you’ll see that the
nonce attribute has no value. However, when you look in the source code (right click > View Page Source) you will see these nonces.
The best way to add the nonce to your Custom HTML tag is to use a DOM Element variable. However, when we use the attribute
nonce you’ll find that it will not work in Chrome. When however we add the nonce also to a data attribute (e.g.
data-nonce) you will find it does work and run your Custom HTML tag. The
data-nonce attribute is a little less secure since it doesn’t have the same browser checks, but alas it is all we can do for now.
Here’s what the output of different variables looks like in Chrome 90.0
And here’s what it looks like in Safari
unsafe-eval to your CSP, thereby negating the whole purpose of the CSP because it is —as it implies— unsafe.
Analysts ❤️ Developers: How to help your developer set up a CSP for the entire site
When removing or invalidating the
'unsafe-inline' argument from your CSP you might get some push back from your developers. First of all it’s good for them to know that their site is getting more secure since, as they themselves have noted, it is no longer possible to execute random code.
The problem is that most of the time they will use a framework that doesn’t work with a CSP out of the box. For example for React you’ll have to set a build variable called
By default, Create React App will embed the runtime script into
index.htmlduring the production build. When set to
false, the script will not be embedded and will be imported as usual. This is normally required when dealing with CSP.
AngularJS has similar options.
If browser compatibility is an issue, you can add the
'unsafe-inline' option as a fallback: newer browsers will ignore it when using nonces, older browsers will still have some protection and functionality when they do not recognise nonces.
Another problem you might run into is that developers love to cache as much as possible. In other words, they want to serve the same version of the site to everyone, but using a nonce (a unique number) implies serving a different page to everyone. One way to solve this is by using something like a Cloudflare service worker, which stands between the user and the origin server to replace anything that has a ‘nonce’ placeholder, with the actual nonce.
Resources and Final Thoughts
Website security is extremely complex and new exploits are uncovered frequently. CSP’s are one way to mitigate those exploits. However, when you allow GTM to run freely on your site, anyone with access to GTM can do anything with your site. In other words, GTM becomes part of a potential supply chain attack. Too often I’ve seen user’s like
[email protected] with unfeathered access to GTM. There’s no way to know who’s behind that or if the email address has been compromised.
- Security researcher Troy Hunt has a great explainer on CSP’s and is in general a good guy to follow for security content
- Google’s developer documentation on implementing GTM with a CSP
- Bounteous on CSP’s and GTM with some extra examples.
- Square on CSP’s for single page applications
- Generating nonces with Cloudflare workers
- A ‘thought experiment’ on dependency confusion and supply-chain attacks
- Some examples of how GTM can be used in XSS attacks. Be aware that whitelisting the GTM domain without your GTM ID and e.g.
unsafe-inlinemeans attackers can also use their own GTM container to inject and run code.