Run TermsFeed Free Cookie Consent under a strict Content Security Policy
Cookie Consent needs one script host, www.termsfeed.com, and permission for one inline <style> element and one inline run() script. A Content Security Policy (CSP) is the HTTP header or <meta> tag that tells the browser which scripts and styles a page is allowed to load. A policy that misses one of these entries hides the consent banner or shows it unstyled, and the Console tab prints a violates the following Content Security Policy directive line for each blocked item.
Hosts and requests Cookie Consent needs
Every request Cookie Consent itself makes goes to https://www.termsfeed.com and loads as a script. Cookie Consent itself does not send a fetch or XMLHttpRequest request or load a stylesheet, font, or image, so connect-src, img-src, and font-src need no entry for it.
Allow each request in the directive it needs.
| Request | URL | Directive |
|---|---|---|
| The loader | https://www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js |
script-src |
| The script itself | https://www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent-code.js |
script-src |
| The ping | https://www.termsfeed.com/public-ping/cookie-consent/4.2.0/cookie-consent.js/<page hostname> |
script-src |
The inline script that calls run() |
Inline | script-src, with a nonce, a hash, or 'unsafe-inline' |
| The styles of the consent banner | One inline <style> element |
style-src, with a nonce or 'unsafe-inline' |
| Released tagged scripts | The hosts your tagged scripts load from | script-src |
The Privacy Policy, Impressum, and credits links are plain links, which the browser follows only on a click. A self-hosted copy replaces the 3 TermsFeed requests with one request to your own host, but it does not get the updates and bug fixes TermsFeed ships. Check why TermsFeed does not recommend it in Self-Host Free Cookie Consent on Your Server.
The minimum Content Security Policy for Cookie Consent
The shortest policy that runs Cookie Consent allows the TermsFeed host for scripts and inline code for scripts and styles. Add the hosts your own tagged scripts load from, such as https://www.googletagmanager.com, to script-src.
Content-Security-Policy: script-src 'self' 'unsafe-inline' https://www.termsfeed.com; style-src 'self' 'unsafe-inline'
This policy lets every inline script and style run, including markup an attacker manages to inject. Use it only where your site already allows 'unsafe-inline', and move to a nonce-based policy otherwise.
A nonce-based Content Security Policy for Cookie Consent
Put the page nonce on the loader tag and on the inline run() script, the 2 script tags the generator writes, and on any script you add in its optional step. A nonce is a random value your server generates for each response, sends in the policy, and writes on every script it trusts.
This nonce-based policy allows only scripts that carry the nonce and the external scripts they add, and styles from your host or with the nonce. Replace R4nd0m with the value your server generates on each request.
Content-Security-Policy: script-src 'nonce-R4nd0m' 'strict-dynamic'; style-src 'self' 'nonce-R4nd0m'; object-src 'none'; base-uri 'self'
This markup shows where the nonce attribute goes in the hosted install.
<script type="text/javascript" src="https://www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js" charset="UTF-8" nonce="R4nd0m"></script>
<script type="text/javascript" charset="UTF-8" nonce="R4nd0m">
document.addEventListener('DOMContentLoaded', function () {
cookieconsent.run({
"notice_banner_type": "headline",
"consent_type": "express",
"palette": "dark",
"language": "en",
"page_load_consent_levels": ["strictly-necessary"]
});
});
</script>
<script type="text/plain" data-cookie-consent="tracking" async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX" nonce="R4nd0m"></script>
<script type="text/plain" data-cookie-consent="tracking" nonce="R4nd0m">
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXX');
</script>
The loader reads the nonce on its own <script> tag and writes it on the cookie-consent-code.js script and the ping it adds. The run() script carries its own nonce, because the browser checks it like any other inline script.
The <style> element of the consent banner takes the nonce from the script that loaded Cookie Consent. Where that script has no nonce, the element takes the nonce from a script whose address contains cookie-consent.js, else from any script with a nonce. Allow that nonce in style-src.
No run() option sets a nonce; a nonce key in the options object is ignored. 'strict-dynamic' makes the browser ignore 'unsafe-inline', so adding 'unsafe-inline' to this policy does not let a released inline script without a nonce run.
Tagged scripts under a strict Content Security Policy
Write the page nonce on every tagged script, as nonce or as data-nonce, and Cookie Consent releases the script with that nonce. Cookie Consent never lends the page nonce to a tagged script without one. A nonce-based policy blocks a released inline script with neither attribute, and a released external script without a nonce loads where the policy lists its host or carries 'strict-dynamic'.
A policy built on host lists instead of nonces needs the host of each released external script in script-src, and 'unsafe-inline' for released inline scripts. Inline event handler attributes such as onload on a tagged script stay blocked under a policy without 'unsafe-inline' or 'unsafe-hashes', so attach those handlers from code. Tag your scripts with Block Scripts Until Consent.
Where each framework puts the page nonce
A framework that generates a nonce for each response hands that nonce to your templates. Read the nonce in your templates from the place your framework puts it.
| Framework | Where the nonce comes from |
|---|---|
| Next.js App Router | The proxy file (proxy.ts) sets the policy and an x-nonce request header, and a Server Component such as the root layout reads it with headers() |
| Angular | The server writes the nonce into index.html, and ngCspNonce on the root element covers the styles Angular adds itself |
| Django with django-csp | request.csp_nonce in the template |
| Ruby on Rails | content_security_policy_nonce in the view, after you set a nonce generator as the Rails security guide shows |
| Express with Helmet | A value you generate per request in res.locals and pass to the script-src directive as a function, as the Helmet README shows |
In a JavaScript framework, the loader, the run() script, and every tagged script sit in the host page. Set up that host page in React, Vue, Angular, SvelteKit, Astro, or Turbo with Single-Page Apps and Frameworks.
How to verify Cookie Consent under your Content Security Policy
Load the page with the policy in place and read the Console tab, which prints a violates the following Content Security Policy directive line for each script or style the policy blocks. Deploy the policy and clear any page cache, then close every private window and run the checks in a new one, so no saved answer hides the consent banner.
- Open Chrome DevTools, select the Console tab, and reload. No line contains
violates the following Content Security Policy directive, and the consent banner shows with its styles. - Select the Network tab, type
termsfeedin the filter box, and reload. The loader,cookie-consent-code.js, and the ping return status 200. - Under a nonce-based policy, turn on
debug: true, publish it, clear any page cache, reload, and read the Console lineNonce for the style tag:, which must show your nonce, notnull. Removedebugafterward. - In the Network tab, change the filter to the host of an optional tagged script, such as
googletagmanager.com. Click the "I agree" button, and check that each tagged external script loads and that no new line in the Console tab containsviolates the following Content Security Policy directive.
The consent banner showed with its styles, and the scripts Cookie Consent released ran under your policy. Work through a consent banner that a policy or a blocker still hides with Ad Blockers and CSP Hide the Cookie Banner.