Configuring Content-Security-Policy (CSP) and allowing Google Tag Manager (GTM) scripts can be split into two main parts:
- Setting GTMs standard tag types.
- Setting GTMs Custom HTML tag types.
The first part will be covered in short notes to provide a handy overview. However, the main concern of this article is the second part, as it is a bit more tricky to set.
Also, note that using unsafe-inline
defeats the whole purpose of CSP, so that's not an option.
0) Nonce
The only practical approach for CSP-allowing is to use the unique server-generated nonce
value, created either via an appropriate library or simply generating the proper random string. The same nonce
value can be used for all scripts, but it must be uniquely generated for each client. For example, generating it in javascript could look like this:
const GENERATED_NONCE = crypto.randomBytes(16).toString("base64");
Add the rule to CSP header and allow generated nonce
:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'nonce-{GENERATED_NONCE}'" />
1) Allowing GTM and it's standard tag types
This part is fairly simple and nicely documented in developers.google.com.
Outlined main steps are:
- Whitelist
nonce
in the CSP header (already done in the previous section of this article). - Use nonce-aware version of GTM snippet - it will propagate the
nonce
to its scripts. - Whitelist necessary resources in the CSP header for the tags used (just follow errors in the console).
In some sense, this should be the end of this article, but unfortunately, GTM doesn't propagate the nonce
to any Custom HTML tags.
2) Setting Custom HTML tag types
Getting the nonce variable in GTM
In order to add the nonce
attribute to the Custom HTML scripts, it must be first defined as a GTM variable:
- Add
id="gtmScript"
to the nonce-aware version of GTM snippet - this will be used to target the element and capturenonce
.
<script id="gtmScript" nonce="{GENERATED_NONCE}">
// GTM function
</script>
- In GTM, create a new variable that will capture the nonce.
Use DOM Element type, and select the ID of the GTM snippet (
gtmScript
in this guide).
Allowing custom HTML script
Now that the nonce
variable is available in the GTM, add it to the Custom HTML script.
<script nonce="{{nonce}}">
console.log("CSP-allowed script with nonce:", "{{nonce}}");
</script>
If the tag is not firing, check the Support document.write. This can be a key step in Single Page Applications.
The GTM Custom HTML script is now nonce-allowed and fires as expected.
Of course, any assets used by this script will now need to be allowed in the CSP header.
Script within a script
Many tracking scripts are creating and firing additional script within themselves.
These will also be blocked as inline-scripts.
Find out where and how they are created, and add nonce
to them as well.
Usually, the code looks similar to this:
var script = document.createElement("script");
script.type = "text/javascript";
script.async = true;
script.src = "https://tracking.js";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(script, s);
Edit this part of the code and insert the nonce
variable, in the same manner along with other attributes.
script.nonce = "{{nonce}}";
Again, pay attention and whitelist any necessary assets that are now being blocked from this newly allowed script.
That's it - Custom HTML script is now fully CSP-allowed.
Top comments (7)
This was so helpful thank you! Googles own docs do not explain needing to add the variable in tag-manager console and adding to the scripts your self.
One problem in your solution is with Chrome. Chrome masks the nonce attribute value so tag manager is unable to grab it and store it as a variable. I solved this by adding a data-nonce attribute and injecting the nonce value there on that same gtmScript element. Works great!
As @ranyehushua points out you have to inject the nonce hash in your own data attribute that doesn't have "nonce" in the name. "data-nonce" will not work as suggested.
Also. Your custom tag can only be evaluated before dom.ready. This means that if you for example use consent and delay tags after cookie_consent_update (in the case of cookiebot) the tag will be blocked by CSP.
Won't exposure of 'nonce' be possible vulnerability?
Yes it is. We opted for not using any nonce-functionality at all in GTM and instead built a CSP-editor in our CSM where it is possible to add new rules on the fly.
This way the marketing department working in GTM can't go nuts adding new stuff indiscriminately, making it easy to keep track of new cookie-producing externally loaded scripts etc. The CSP-rules becomes less of a hurdle and more a nifty way of book keeping externally loaded scripts.
Hi, thanks for the article! However I wasn't able to propagate nonce value to my custom HTML tag. Apparently it doesn't work when GTM tries to read
nonce
attribute from the script tag. However, several articles suggested slightly different approach by using thedata-nonce
property on a script tag. This would imply modifying the GTM variable configuration to read fromdata-nonce
instead ofnonce
property. I tried this and my custom HTML tag fired.Any idea on how I could modify GTM config so that I don't have to use this patchy
data-nonce
property?The whole purpose of using nonces with the CSP is to generate a nonce PER RESPONSE not per client. What is the best approach to update the nonce variable on the GTM side in the proper configuration?
The approach in this article will always give a unique nonce, i.e. refreshing the page generates a new nonce.