# Internal GA template triggers CSP

**URL:** <https://discourse.gohugo.io/t/internal-ga-template-triggers-csp/19740>\
**Category:** tips & tricks\
**Created:** [July 21, 2019, 2:24pm UTC](https://discourse.gohugo.io/t/internal-ga-template-triggers-csp/19740 "2019-07-21T14:24:25Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![TotallyInformation](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/totallyinformation/32/5668_2.png) [@TotallyInformation](https://discourse.gohugo.io/u/TotallyInformation)\
**Post date:** [July 21, 2019, 2:24pm UTC](https://discourse.gohugo.io/t/internal-ga-template-triggers-csp/19740/1 "2019-07-21T14:24:25Z")

</div>

The internal GA template includes inline code which is not good from a security perspective as it means that you need to include ‘unsafe-inline’ in your CSP header which will get you badly marked down on security checkers and for good reason since any malware that can insert code will be able to run.

```auto
script-src 'self' 'unsafe-inline'

```

I believe that it would be much better to put this code into an external script file. I realise that I can do this myself, and I am doing. But I thought this might be useful to others who might puzzle over why things are going strange with their site in relation to Google Analytics.

> <https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates/google_analytics_async.html>

---

<div class="post-metadata">

**Author:** ![davidsneighbour](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/davidsneighbour/32/24507_2.png) [@davidsneighbour](https://discourse.gohugo.io/u/davidsneighbour)\
**Post date:** [July 21, 2019, 2:57pm UTC](https://discourse.gohugo.io/t/internal-ga-template-triggers-csp/19740/2 "2019-07-21T14:57:16Z")

</div>

for the record, this is my google analytics template, which makes checks happy:

```auto
{{- $pc := .Site.Config.Privacy.GoogleAnalytics -}}
{{- if not $pc.Disable -}}
{{ with .Site.GoogleAnalytics }}
<script type="application/javascript">
function loadGoogleAnalyticsAsync(){
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = 'https://www.google-analytics.com/analytics.js';
    document.body.appendChild(script);
}
{{ template "__ga_js_set_doNotTrack" $ }}
if (!doNotTrack) {	
	document.addEventListener('readystatechange', event => {
	    if (event.target.readyState === "complete") {
	        setTimeout("loadGoogleAnalyticsAsync()", 3000);
	    }
	});
	window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
	{{- if $pc.UseSessionStorage }}
	if (window.sessionStorage) {
		var GA_SESSION_STORAGE_KEY = 'ga:clientId';
		ga('create', '{{ . }}', {
			'storage': 'none',
			'clientId': sessionStorage.getItem(GA_SESSION_STORAGE_KEY)
		});
		ga(function(tracker) {
			sessionStorage.setItem(GA_SESSION_STORAGE_KEY, tracker.get('clientId'));
		});
	}
	{{ else }}
	ga('create', '{{ . }}', 'auto');
	{{ end -}}
	{{ if $pc.AnonymizeIP }}ga('set', 'anonymizeIp', true);{{ end }}
	ga('send', 'pageview');
}
</script>
{{ end }}
{{- end -}}

```

it’s loading 3 seconds after page load. you can change that at `setTimeout("loadGoogleAnalyticsAsync()", 3000);`. It’s compatible with all the privacy settings of hugo.

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/totallyinformation/32/5668_2.png) [@TotallyInformation](https://discourse.gohugo.io/u/TotallyInformation)\
**Post date:** [July 21, 2019, 3:02pm UTC](https://discourse.gohugo.io/t/internal-ga-template-triggers-csp/19740/3 "2019-07-21T15:02:56Z")

</div>

> [@davidsneighbour](#):
>
> It’s compatible with all the privacy settings of hugo.

But it will still trigger a CSP unless ‘unsafe-inline’ is included won’t it? I can’t see anything different there that would stop it. In fact, that might trigger other malware checks since you are behaving just like some malware by dynamically adding a script after the page has loaded.

This would all be fixed by simply moving the in-line script to a file and loading that instead since then you only need `script-src: 'self';` in your CSP which is pretty normal.

I just need to work out how to create a script resource with dynamic content - been a while since I looked at Hugo since my site generally just works! 🙂 - apart from the CSP which has been annoying me for a while.

---

<div class="post-metadata">

**Author:** ![davidsneighbour](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/davidsneighbour/32/24507_2.png) [@davidsneighbour](https://discourse.gohugo.io/u/davidsneighbour)\
**Post date:** [July 21, 2019, 3:14pm UTC](https://discourse.gohugo.io/t/internal-ga-template-triggers-csp/19740/4 "2019-07-21T15:14:06Z")

</div>

Yes. But it would be interesting to solve this thing. The partial should create a string. That string should be able to be transformed into an hash code. That hash could be added to your CSP and then everything would be fine.

You could still without that add everything in the script into your own js file.

I am creating my CSP with a template for netlify. So “technically” it should be possible.

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/totallyinformation/32/5668_2.png) [@TotallyInformation](https://discourse.gohugo.io/u/TotallyInformation)\
**Post date:** [July 21, 2019, 3:19pm UTC](https://discourse.gohugo.io/t/internal-ga-template-triggers-csp/19740/5 "2019-07-21T15:19:03Z")

</div>

OK, now I’m interested 😄

The alternative to a hash would be a dynamically created nonce. This should be a string of at least 128 bits.

Either way, how do we link the dynamically created hash/nonce to the Netlify header? I’m specifying the header in `netlify.toml` in the root folder of my sites source code.

---

<div class="post-metadata">

**Author:** ![davidsneighbour](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/davidsneighbour/32/24507_2.png) [@davidsneighbour](https://discourse.gohugo.io/u/davidsneighbour)\
**Post date:** [July 21, 2019, 3:28pm UTC](https://discourse.gohugo.io/t/internal-ga-template-triggers-csp/19740/6 "2019-07-21T15:28:13Z")

</div>

In your config.toml:

```auto
[outputs]
home = ["HTML", "RSS", "Algolia", "REDIR", "HEADERS"]

[outputFormats.HEADERS]
  mediatype = "text/netlify"
  baseName = "_headers"
  isPlainText = true
  notAlternative = true

```

The `HEADERS` part is important, ignore all else you don’t already have

Then in `_default/index.header` add your template for the headers file in netlify. Mine looks very specific, but should explain some points. The `with partial footer/js.html` is the place where the hash is created for my preloaded script. the same use cold be done for adding a CSP hash for the script.

```auto
/
  Accept-Encoding: gzip, deflate, br
  Link: </assets/fonts/panton/panton-regular-webfont.woff2>; rel=preload; as=font; crossorigin; type="font/woff2"
  Link: </assets/fonts/panton/panton-regularitalic-webfont.woff2>; rel=preload; as=font; crossorigin; type="font/woff2"
  Link: </assets/fonts/panton/panton-black-webfont.woff2>; rel=preload; as=font; crossorigin; type="font/woff2"
  Link: </assets/fonts/panton/panton-blackitalic-webfont.woff2>; rel=preload; as=font; crossorigin; type="font/woff2"
  Link: </assets/fonts/panton/panton-heavy-webfont.woff2>; rel=preload; as=font; crossorigin; type="font/woff2"
  {{ with partial "footer/js.html" . }}
  Link: <{{ .RelPermalink }}>; rel=preload; as=script; integrity='{{ .Data.Integrity }}'
  {{ end }}
  {{ with partialCached "head/css.html" . }}
  Link: <{{ .RelPermalink }}>; rel=preload; as=style; integrity='{{ .Data.Integrity }}'
  {{ end }}
  Feature-Policy: geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; fullscreen 'none'; payment 'none'

/*
  X-Frame-Options: DENY
  X-XSS-Protection: 1; mode=block
  Referrer-Policy: no-referrer
  X-Content-Type-Options: nosniff
  Content-Security-Policy: default-src 'self' googleads.g.doubleclick.net; script-src 'self' 'unsafe-inline' 'unsafe-eval' pagead2.googlesyndication.com storage.googleapis.com googleads.g.doubleclick.net ajax.googleapis.com www.google-analytics.com www.google.com www.gstatic.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: www.google-analytics.com; connect-src *.algolia.net *.algolianet.com; frame-src www.google.com www.youtube.com googleads.g.doubleclick.net; upgrade-insecure-requests; manifest-src 'self'; font-src 'self' fonts.googleapis.com; frame-ancestors 'self'; object-src 'self'

/*.html
  Accept-Encoding: gzip, deflate, br

/*.manifest
  Content-Type: application/manifest+json; charset=utf-8  
  Cache-Control: public, max-age=31536000, immutable
  Accept-Encoding: gzip, deflate, br

/script*.js
  Expires: Tue, 31-Dec-2020 23:59:59 GMT+0700
  Content-Type: text/javascript; charset=utf-8
  Cache-Control: public, max-age=31536000, immutable
  Accept-Encoding: gzip, deflate, br

/style*.css
  Expires: Tue, 31-Dec-2020 23:59:59 GMT+0700
  Cache-Control: public, max-age=31536000, immutable
  Accept-Encoding: gzip, deflate, br

/*.jpg
  Expires: Tue, 31-Dec-2020 23:59:59 GMT+0700
  Cache-Control: public, max-age=31536000, immutable
  Accept-Encoding: gzip, deflate, br

/*.png
  Expires: Tue, 31-Dec-2020 23:59:59 GMT+0700
  Cache-Control: public, max-age=31536000, immutable
  Accept-Encoding: gzip, deflate, br

/assets/*
  Expires: Tue, 31-Dec-2020 23:59:59 GMT+0700
  Cache-Control: public, max-age=31536000, immutable

/images/*
  Expires: Tue, 31-Dec-2020 23:59:59 GMT+0700
  Cache-Control: public, max-age=31536000, immutable

```

I think this topic is interesting for my website too, but it might take some days to implement it properly. If you can wait, then wait. Else: be an explorer and tell us how to do it 🙂

---

<div class="post-metadata">

**Author:** ![davidsneighbour](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/davidsneighbour/32/24507_2.png) [@davidsneighbour](https://discourse.gohugo.io/u/davidsneighbour)\
**Post date:** [July 21, 2019, 3:30pm UTC](https://discourse.gohugo.io/t/internal-ga-template-triggers-csp/19740/7 "2019-07-21T15:30:52Z")

</div>

just in case it’s not obvious: you can put your js code into a file and then create the hash from it, this is the `footer/js.html` file:

```auto
{{/*

JS creation for samui-samui.de

-
- SRI is applied via Fingerprint
- that's all for now. but it's enough, isn't it?

*/}}

{{ $js0 := resources.Get "js/libs/jquery.js" }}
{{ $js1 := resources.Get "js/libs/popper.js" }}
{{ $js2 := resources.Get "js/libs/bootstrap.js" }}
{{ $js32 := resources.Get "js/libs/de.js" }}
{{ $js31 := resources.Get "js/libs/moment.js" }}
{{ $js4 := resources.Get "js/libs/instantsearch.js" }}
{{ $js5 := resources.Get "js/youtube-embedder.js" }}
{{ $js6 := resources.Get "js/soundcloud.js" }}
{{ $js7 := resources.Get "js/progress-indicator.js" }}
{{ $js8 := resources.Get "js/search.js" }}
{{ $js9 := resources.Get "js/script.js" }}
{{ $js10 := resources.Get "js/cookieconsent.min.js" }}
{{ $js := slice $js0 $js31 $js4 $js5 $js6 $js7 $js8 $js9 $js10 | resources.Concat "script.js" | resources.Minify | resources.Fingerprint "sha384" }}

{{ partial "structured-data/organisation" . }}
{{ partial "structured-data/person" . }}
{{ partial "structured-data/sitesearch" . }}

{{ return $js }}

```

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/totallyinformation/32/5668_2.png) [@TotallyInformation](https://discourse.gohugo.io/u/TotallyInformation)\
**Post date:** [July 21, 2019, 3:42pm UTC](https://discourse.gohugo.io/t/internal-ga-template-triggers-csp/19740/8 "2019-07-21T15:42:15Z")

</div>

I have limited time unfortunately and far too many things to try and get through however, I am looking at this. I will see if I can get this working for myself but also look forward to seeing your results.

I’m excited as this may well finish off the outstanding annoyances of my site which I moved from Wordpress to Hugo over a year ago now.

I also have 2 more sites to set up but I also have some open source software to update - this isn’t even my day-job ☹

> [@davidsneighbour](#):
>
> just in case it’s not obvious: you can put your js code into a file and then create the hash from it

Ah, I think I did this for some other script file - hard to remember - I will check that out.

---

<div class="post-metadata">

**Author:** ![davidsneighbour](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/davidsneighbour/32/24507_2.png) [@davidsneighbour](https://discourse.gohugo.io/u/davidsneighbour)\
**Post date:** [July 21, 2019, 3:46pm UTC](https://discourse.gohugo.io/t/internal-ga-template-triggers-csp/19740/9 "2019-07-21T15:46:30Z")

</div>

I moved my Wordpress site a year ago to Hugo and got 100/100 on GTMetrix now. It’s quite easy. Most of the time “annoyances” are just personal perks.

As you can see my CSP allows all the insecure stuff… Let’s fix that…

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/totallyinformation/32/5668_2.png) [@TotallyInformation](https://discourse.gohugo.io/u/TotallyInformation)\
**Post date:** [July 21, 2019, 4:02pm UTC](https://discourse.gohugo.io/t/internal-ga-template-triggers-csp/19740/10 "2019-07-21T16:02:09Z")

</div>

Here is my CSP so far:

```auto
upgrade-insecure-requests;
frame-ancestors 'none';
child-src 'self' https://disqus.com/ https://www.slideshare.net/;
report-uri https://totallyinfo.report-uri.com/r/d/csp/wizard;

default-src 'self';
style-src 'self' 'unsafe-inline' https://c.disquscdn.com https://www.google.com/cse/static/;
img-src 'self' data: https://*.cloudfront.net https://c.disquscdn.com https://referrer.disqus.com https://stackexchange.com/users/flair/1375993.png https://www.google-analytics.com https://www.googletagmanager.com/a;
media-src 'self' https://youtube.com;
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google-analytics.com/ https://*.disqus.com https://disqus.com https://c.disquscdn.com https://ajax.cloudflare.com https://www.googletagmanager.com/gtag/js https://cse.google.com/cse.js https://www.google.com/cse/static/element/ https://local.adguard.org;
base-uri 'none'; object-src 'none';
connect-src 'self' wss://local.adguard.org/adguard-ws-api/api;
font-src 'self' data:;

```

Obviously we want to remove the ‘unsafe-inline’.

Some oddities in there that bear explainging. References to `adguard` deal with AdGuard for Windows that I’m trialling at the moment (that is also the reason for `data:` on the font-src). Disqus should be obvious from our other thread 😉. Google references cover GA apart from the inline script. Cloudfront is because I use Netlify’s smart image handling. StackExchange is to allow me to dynamically load my SE badge.

I may be able to trim that down a little more as I think some of those entries are only needed for my search page which uses a Google site search form. Yes, I know I need to fix that up and use Algolia or some such but I failed miserably last time I tried and I just haven’t had the motivation to fix that.

I also need some special care on a few other pages.

#### Post page with embedded Slideshare

Seems to need `Access-Control-Allow-Origin = "*"` instead of my default `Access-Control-Allow-Origin = "https://it.knightnet.org.uk"`

#### Contact form

I use Netlify forms for this and have to use reCapture to stop the spam. So that needs a different CSP:

```auto
upgrade-insecure-requests; 
frame-ancestors 'none'; child-src https://www.google.com/; 
report-uri https://totallyinfo.report-uri.com/r/d/csp/wizard;

```

#### Search form

Uses Google site search as mentioned.

#### Redirects

As my site has been around a long time and was merged from several older sites to WordPress then migrated from that to Hugo, I have a lot of old URL’s that I forward to better locations. Every time I try to remove those, I get lots of issues.

I also have some “shortcut” redirects that take me to other places, easier than trying to remember all of the URL’s. I actually have another domain registered that I’m going to use for short URL’s.

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/totallyinformation/32/5668_2.png) [@TotallyInformation](https://discourse.gohugo.io/u/TotallyInformation)\
**Post date:** [July 21, 2019, 4:53pm UTC](https://discourse.gohugo.io/t/internal-ga-template-triggers-csp/19740/11 "2019-07-21T16:53:43Z")

</div>

OK, as I was looking into it all, I realised that GA **REALLY** isn’t worth it - it is GONE! The crap that it dynamically loads makes a complete mockery of any security.
