Content Security Policy (CSP)
Introduction
Specifying a Content Security Policy (CSP) is a common way to secure web applications. This policy controls which scripts and styles can run on a page. You can implement it by adding a Content-Security-Policy header or using a meta tag.
Content Security Policy nonce setup
It's common practice to allow only scripts and styles from trusted domains or those with a special nonce attribute. The nonce mechanism involves two steps: defining the nonce value in the Content Security Policy and adding the same nonce value as an attribute in the styles and scripts.
Define a nonce in Content Security Policy settings
The nonce mechanism requires an additional definition in the script-src directive of the Content Security Policy:
script-src <your-sources> 'nonce-INSERT_VALID_NONCE_VALUE';
Note: The nonce value should be generated server-side and must be unique for each request. Note that
<your-sources>
is a placeholder for your permitted sources.
Add the nonce to the container code
As a result, the default container code requires the following modifications to function properly.
Asynchronous snippet: The following changes are required in this container code:
<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),isStgDebug&&qP.push("stg_debug");
var qPString=qP.length>0?("?"+qP.join("&")):"";
tags.async=!0,tags.src="//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>
<noscript>
<iframe src="//client.containers.piwik.pro/feacd61d-0232-40a1-96c3-7e469f7bfa7f/noscript.html" height="0" width="0" style="display:none;visibility:hidden"></iframe>
</noscript>
Synchronous snippet (deprecated): The following changes are required in this container code:
<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="//client.containers.piwik.pro/'+id+'.sync.js' + qPString + '" nonce="INSERT_VALID_NONCE_VALUE"></' + 'script>');
})(window, document, 'dataLayer', 'feacd61d-0232-40a1-96c3-7e469f7bfa7f');
</script>
Note: To make sure Tag Manager works, replace
INSERT_VALID_NONCE_VALUE
with the generated nonce value. This needs to be done twice—once for the asynchronous snippet and once for the synchronous snippet.
Adjust tags to work with the Content Security Policy
Asynchronous tags: In most cases, no changes are needed to make asynchronous tags work. Tag Manager will automatically insert the nonce attribute into all fired tags. The only exception is when your tag adds additional scripts or styles to the page; in such cases, you should manually add the nonce attribute.
Synchronous tags: Since synchronous tags must fire before the entire page loads, follow this recommended procedure:
- Create a new variable for the nonce parameter. You don’t need to create the nonce variable in the admin panel; simply push it to the dataLayer before the script executes.
window.dataLayer.push({
nonce: INSERT_VALID_NONCE_VALUE
});
- Use the created variable as the value for the nonce attribute, as shown below:
<script nonce="{{ nonce }}">
console.log("I'm synchronous tag!");
document.write('<p id="synchronous-tag">I was inserted by synchronous tag</p>');
</script>
Note:Not all third-party tools available as built-in templates are configured to work with Content Security Policy. This includes tools like Google Analytics. In these cases, refer to the documentation for each tool (e.g., https://developers.google.com/web/fundamentals/security/csp).
Tag Manager debugger
To load all necessary assets from the Tag Manager debugger, define sources using the img-src
, font-src
and style-src
directives.
img-src <your-sources> client.containers.piwik.pro;
font-src <your-sources> client.containers.piwik.pro;
style-src <your-sources> client.containers.piwik.pro;
Consent Manager form assets
If your website is GDPR compliant then you need to describe the connect-src
, style-src
and img-src
directives:
connect-src <your-sources> client.piwik.pro client.containers.piwik.pro;
style-src <your-sources> 'nonce-INSERT_VALID_NONCE_VALUE';
Note:We define the tracking domain as
client.piwik.pro
for collecting visitor consents and the container domain asclient.containers.piwik.pro
for fetching consent form assets.
Consent Manager’s data subject request widget
When using a data subject request widget, you need to add a nonce attribute to its <script>
tag.
<div id="ppms_cm_data_subject" class="ppms_cm_data_subject_widget__wrapper" data-editor-centralize="true" data-main-container="true" data-root="true">
<h3 id="ppms_cm_data_subject_header" class="header3">Data requests</h3>
<p id="ppms_cm_data_subject_paragraph" class="paragraph">
Please select below the type of data request along with any special requests in the body of the message. (...)
</p>
<form id="ppms_cm_data_subject_form" class="ppms_cm_data_subject_form" data-disable-select="true">
...
</form>
<script nonce="INSERT_VALID_NONCE_VALUE">
...
</script>
</div>
JavaScript tracking client
To load all the necessary assets for the JavaScript tracking client, define the sources for script-src
, img-src
and connect-src
.
script-src <your-sources> https://client.piwik.pro/ppms.js;
img-src <your-sources> https://client.piwik.pro/ppms.php;
connect-src <your-sources> https://client.piwik.pro/ppms.php;
Note: You may need to adjust these paths if you're not using the default settings. For example, the JavaScript library might be loaded as an alternative build that doesn't conflict with the Matomo script:
https://client.piwik.pro/ppas.js
.
Tracking with a custom domain
If you have a custom tracking domain, define it using the img-src
and script-src
directives:
img-src <your-sources> your-custom-cpp-domain.com;
script-src <your-sources> your-custom-cpp-domain.com;
Allowing the site inspector extension to work on the site
Our site inspector is a Chrome browser extension that visualizes analytics data like clickmaps, heatmaps and scrollmaps on tracked pages. By default, the JavaScript tracking client adds the necessary configuration for this extension in the page's HTML. However, you can disable this behavior if needed using setSiteInspectorSetup.
Since the extension integrates with the page it runs on, additional CSP rules are required to ensure it functions properly.
connect-src <your-sources+necessary-Piwik-PRO-sources> https://client.piwik.pro/api/;
frame-src <your-sources+necessary-Piwik-PRO-sources> https://client.piwik.pro/site-inspector/;
Note: If you're using a custom domain for tracking, make sure to adjust the domain in the CSP rules accordingly.
Example Content Security Policy definition
The following example Content Security Policy setup assumes:
- Your website address: client.com
- Your account name in Piwik PRO: client
- Your container domain: client.containers.piwik.pro
- You have a Piwik PRO tag with the default tracking domain: client.piwik.pro
- Nonce value: nceIOfn39fn3e9h3sd
- The setup allows the
self' source
which is client.com
Content-Security-Policy: default-src 'self';
script-src 'self' client.piwik.pro 'nonce-nceIOfn39fn3e9h3sd';
connect-src 'self' client.containers.piwik.pro client.piwik.pro;
img-src 'self' client.containers.piwik.pro client.piwik.pro;
font-src 'self' client.containers.piwik.pro;
style-src 'self' client.containers.piwik.pro 'nonce-nceIOfn39fn3e9h3sd';
Updated 15 days ago