Harden the JavaScript tracker against compromised scripts
The JavaScript tracker runs in your page, so every script it loads, including sp.js, plugins, and libraries that plugins load, runs with the same access as your own code. If a CDN or an upstream package serving one of those scripts is compromised, the change reaches your page the next time the script loads.
This page describes three measures that limit this exposure. You can apply them independently, but they work best together.
Self-host or bundle the tracker and plugins
When you load sp.js or a plugin from a third-party CDN, the browser runs whatever that CDN serves at that URL. Hosting the files yourself means they only change when you deploy a new version.
You can remove third-party script loading for the tracker and its plugins in any of these ways:
- Self-host
sp.jsby following the hosting guides - Install the tracker and plugins from npm and bundle them into your application
- Create a custom build of
sp.jsthat includes only the plugins you use
If you load plugins by URL using addPlugin, self-host the plugin files too, or pin each URL to an exact version, such as @4.10.2. Never use @latest or a major-version range such as @4 in production, since the file behind those URLs changes with each release.
Restrict which origins can serve scripts
A Content Security Policy (CSP) tells the browser which origins your page can load scripts from and send requests to. The browser blocks a script from any other origin, even if the script is injected into your page.
Add the origins the tracker needs to your existing policy. The tracker needs script-src to allow the origin you load sp.js and plugins from, and connect-src to allow your Collector. For example, if you self-host the tracker and your Collector is at collector.example.com:
Content-Security-Policy: script-src 'self'; connect-src 'self' https://collector.example.com
The tag snippet that loads sp.js is an inline script, so a policy without 'unsafe-inline' also needs to allow the snippet with a nonce or hash.
Some plugins load additional scripts that also need to be allowed:
- Web Vitals plugin: loads the
web-vitalslibrary fromhttps://unpkg.comby default. Allow that origin, or self-host the library and set thewebVitalsSourceoption to its URL - YouTube tracking plugin: loads the YouTube IFrame Player API from
https://www.youtube.com
Verify scripts with Subresource Integrity
Subresource Integrity (SRI) lets the browser check that a script matches a hash you provide, and refuse to run it if it doesn't. SRI only works with a URL that points to an exact version, because the hash changes whenever the file changes.
To get the hash for an exact version on jsDelivr, open the package page on jsDelivr and copy the SRI hash for the file. For a file you host yourself, compute the hash:
openssl dgst -sha384 -binary sp.js | openssl base64 -A
The standard tag snippet creates the script element in JavaScript, so set the hash on that element rather than on a <script> tag. This version of the snippet adds n.integrity and n.crossOrigin before the script loads:
<script type="text/javascript" async=1>
;(function(p,l,o,w,i,n,g){if(!p[i]){p.GlobalSnowplowNamespace=p.GlobalSnowplowNamespace||[]; p.GlobalSnowplowNamespace.push(i);p[i]=function(){(p[i].q=p[i].q||[]).push(arguments) };p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1; n.integrity="sha384-{{hash}}"; n.crossOrigin="anonymous"; n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","https://cdn.jsdelivr.net/npm/@snowplow/javascript-tracker@{{version}}/dist/sp.js","snowplow"));
</script>
When you upgrade the tracker, update the version and the hash together. If they don't match, the browser doesn't run the tracker and no events are sent.
Understand what these measures don't cover
SRI checks only the script that has the integrity attribute. It doesn't check scripts that script loads afterwards.
The YouTube tracking plugin loads the IFrame Player API from https://www.youtube.com/iframe_api. Google doesn't version this file or publish a hash for it, so you can't pin it or verify it with SRI. If you use the YouTube plugin, your page runs whatever version of the API Google serves, which is also true of any page that embeds the YouTube player.