1

I'm implementing CSP on an existing website and have been following along with this article on passing a CSP nonce to GTM and using it as a Custom Variable in GTM.

<script nonce="9CZ9vGge7C9At2iwrPtSNG7Ev10=" id="gtmScript">
<!-- Google Tag Manager -->
(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;var n=d.querySelector('[nonce]');
n&&j.setAttribute('nonce',n.nonce||n.getAttribute('nonce'));f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-MYID');
<!-- End Google Tag Manager -->
</script>

Variable gets added as a DOM Element Variable. Then I add the variable value to my custom script. Here is a demo script. It is the entirety of a Custom HTML tag in GTM.

<script nonce="{{nonce}}">
  console.log("CSP-allowed script with nonce:", "{{nonce}}");
</script>

The issue is, CSP still blocks this. And it has nothing to do with the {{nonce}} variable - proven by changing CSP to 'unsafe-inline' and seeing the correct value output in console.

I have since been reading that GTM strips attributes out of the tags it injects inline. Which would be odd, as it would mean the linked article above actually never would have worked (it's only a 3 month old article). But does this mean that it's physically impossible to get scripts in Custom HTML GTM tags to function with CSP? The solution in the above link is impossible because the script will always be blocked by CSP.

UPDATE: Here is my CSP

<meta http-equiv="Content-Security-Policy" content="
            default-src 'none' ;frame-src 'self';
            script-src 'self' 'nonce-$CSPNonce' *.googletagmanager.com;
            style-src 'self' 'nonce-$CSPNonce';
            font-src 'self';
            img-src 'self' 'nonce-$CSPNonce' data:;
            connect-src 'self'">

And the console error

gtm.js?id=GTM-MYID:782 Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' 'nonce-mQoPSCSszFQ8loJF5jii6quCHeY=' *.googletagmanager.com". Either the 'unsafe-inline' keyword, a hash ('sha256-3kt898DvY8z+SqQyfz8g06pUzzBokMjvzcQ5uN50wTs='), or a nonce ('nonce-...') is required to enable inline execution.

Aaryn
  • 1,601
  • 2
  • 18
  • 31
  • `"The issue is, CSP still blocks this"` - could you show: 1). the violation message in the browser console; 2). CSP you do use. – granty Mar 10 '21 at 17:05
  • Hi @granty, I have made an update to my question with this info. The nonce is definitely making it into GTM. It just appears that the `nonce` attribute on the – Aaryn Mar 10 '21 at 19:01

2 Answers2

6

OK I found the cause here. I'm leaving the question up in case someone stumbles across this in future.

When you create a Custom HTML tag in GTM, under the code window is a tickbox called "Support document.write". The tooltip beside it doesn't mention much other than allowing you to use document.write() in your scripts via a "new rendering engine".

For whatever reason, if this is not ticked, the nonce attribute is stripped. With it ticked (using the new rendering method I guess), it is not stripped.

enter image description here

Aaryn
  • 1,601
  • 2
  • 18
  • 31
  • This is entirely non-obvious. Been pulling my hair out for hours thinking I'd misconfigured a hundred other things. No, it was this checkbox. How bizarre. Thanks! – Patrick Aug 08 '23 at 15:10
0

You must both turn on support for document.write AND use an additional alternate name for your nonce attribute. Between GTA and chrome your nonce value will be eaten for security reasons.

<script nonce="NONCEHERE" data-dsn="NONCEHERE" id="gtmScript">
<!-- Google Tag Manager -->
(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;var 
n=d.querySelector('[nonce]');n&&j.setAttribute('nonce',n.nonce||n.getAttribute('nonce'));f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-MYID');
<!-- End Google Tag Manager -->
</script>

Then use the data-dsn attribute to pull it into GTM.

Google Tag Manager Variable Entry