Content Security Policy (CSP)

Introduction

Specifying Content Security Policy is a common way to secure web applications. This mechanism allows specifying which scripts and styles can execute on page. It can be done either by adding a Content-Security-Policy header or an appropriate meta tag.

You can read about Consent Security Policy here: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

Content Security Policy nonce configuration

It is common to allow only scripts and styles that were received from known domains or ones that have special nonce attribute. Nonce mechanism relies on two steps, defining nonce value in Content Security Policy and placing nonce value as an attribute in styles and scripts.

Defining nonce in Content Security Policy settings

Nonce mechanism requires additional definition in script-src directive of Content Security Policy:

script-src <your-sources> 'nonce-INSERT_VALID_NONCE_VALUE';

Note

Nonce value should be generated on the server-side. Its value should be different for each request. Please note that we leave here space for your permitted sources <your-sources>.

Adding nonce to container code

Consequently, default container code requires following modifications to work:

  • asynchronous snippet - given container code following changes (highlighted) are required:

    <script type="text/javascript" nonce="INSERT_VALID_NONCE_VALUE">
        (function(window, document, dataLayerName, id) {
        window[dataLayerName]=window[dataLayerName]||[],window[dataLayerName].push({start:(new Date).getTime(),event:"stg.start"});
        var scripts=document.getElementsByTagName('script')[0],tags=document.createElement('script');
        function stgCreateCookie(a,b,c){var d="";if(c){var e=new Date;e.setTime(e.getTime()+24*c*60*60*1e3),d=";expires="+e.toUTCString()}document.cookie=a+"="+b+d+"; path=/"}
        var isStgDebug=(window.location.href.match("stg_debug")||document.cookie.match("stg_debug"))&&!window.location.href.match("stg_disable_debug");
        stgCreateCookie("stg_debug",isStgDebug?1:"",isStgDebug?14:-1);
        var qP=[];dataLayerName!=="dataLayer"&&qP.push("data_layer_name="+dataLayerName),tags.nonce="INSERT_VALID_NONCE_VALUE",isStgDebug&&qP.push("stg_debug");
        var qPString=qP.length>0?("?"+qP.join("&")):"";
        tags.async=!0,tags.src="https://client.containers.piwik.pro/"+id+".js"+qPString,
        scripts.parentNode.insertBefore(tags,scripts);
        !function(a,n,i){a[n]=a[n]||{};for(var c=0;c<i.length;c++)!function(i){a[n][i]=a[n][i]||{},a[n][i].api=a[n][i].api||function(){
        var a=[].slice.call(arguments,0);"string"==typeof a[0]&&window[dataLayerName].push({event:n+"."+i+":"+a[0],parameters:[].slice.call(arguments,1)})}}(i[c])}(window,"ppms",["tm","cm"]);
        })(window, document, 'dataLayer', 'feacd61d-0232-40a1-96c3-7e469f7bfa7f');
    </script>
    
  • synchronous snippet - following changes (highlighted) are required:

    <script type="text/javascript" nonce="INSERT_VALID_NONCE_VALUE">
        (function(window, document, dataLayerName, id) {
        function stgCreateCookie(a,b,c){var d="";if(c){var e=new Date;e.setTime(e.getTime()+24*c*60*60*1e3),d=";expires="+e.toUTCString()}document.cookie=a+"="+b+d+"; path=/"}
        var isStgDebug=(window.location.href.match("stg_debug")||document.cookie.match("stg_debug"))&&!window.location.href.match("stg_disable_debug");
        stgCreateCookie("stg_debug",isStgDebug?1:"",isStgDebug?14:-1);
        var qP=[];dataLayerName!=="dataLayer"&&qP.push("data_layer_name="+dataLayerName),isStgDebug&&qP.push("stg_debug");
        var qPString=qP.length>0?("?"+qP.join("&")):"";
        document.write('<script src="https://client.containers.piwik.pro/'+id+'.sync.js' + qPString + '" nonce="INSERT_VALID_NONCE_VALUE"></' + 'script>');
        })(window, document, 'dataLayer', 'feacd61d-0232-40a1-96c3-7e469f7bfa7f');
    </script>
    

Note

All that is needed for Tag Manager to work is to replace INSERT_VALID_NONCE_VALUE with generated nonce value. It should be done twice for both asynchronous and synchronous snippet.

Adjusting tags to work with Content Security Policy

  • asynchronous tags - in most cases there should not be any change required to make asynchronous tags work. Tag Manager will automatically insert nonce attribute to all fired tags. Only exceptions is when Your tag adds other scripts/styles on page by itself - in such case, You should add nonce attribute manually.

  • synchronous tags - since synchronous tags have to fire before whole page is loaded, following procedure is recommended:

    1. Create new variable with value of nonce parameter. It is not required to create nonce variable in admin panel. Just pushing it on dataLayer before script is executed is enough.

      window.dataLayer.push({
          nonce: INSERT_VALID_NONCE_VALUE
      });
      
    2. Use created variable as value for nonce attribute like follows:

      <script nonce="{{ nonce }}">
          console.log("I'm synchronous tag!");
          document.write('<p id="synchronous-tag">I was inserted by synchronous tag</p>');
      </script>
      

Note

Finally, not all 3rd party tools that are available as built-in templates are adjusted to work with Content Security Policy. This includes e.g. Google Analytics. In such cases, please refer to documentation of each respective tool (e.g. https://developers.google.com/web/fundamentals/security/csp).

Tag Manager debugger

To load all necessary assets from Tag Manager debugger you need to define source with img-src, font-src and style-src directives:

img-src <your-sources> https://client.containers.piwik.pro;
font-src <your-sources> https://client.containers.piwik.pro;
style-src <your-sources> https://client.containers.piwik.pro;

Tracking with custom domain

If your tracking domain is custom, then you need to define it with img-src and script-src directives:

img-src <your-sources> https://your-custom-domain.com;
script-src <your-sources> https://your-custom-domain.com/ppms.js;

Example Content Security Policy definition

Following example configuration of CSP assumes:

  • client’s website address: client.com
  • Consent Manager is enabled for the website
  • client’s organization name in Piwik PRO: client
  • client’s container domain: client.containers.piwik.pro
  • client has Piwik PRO tag with default tracking domain: client.piwik.pro
  • nonce value: nceIOfn39fn3e9h3sd
  • configuration allows 'self' source which is: client.com
Content-Security-Policy: default-src 'none';
                         script-src  'self' https://client.piwik.pro/ppms.js 'nonce-nceIOfn39fn3e9h3sd';
                         connect-src 'self' https://client.containers.piwik.pro https://client.piwik.pro;
                         img-src     'self' https://client.containers.piwik.pro https://client.piwik.pro;
                         font-src    'self' https://client.containers.piwik.pro;
                         style-src   'self' https://client.containers.piwik.pro 'nonce-nceIOfn39fn3e9h3sd';