Content Security Policy and Your CMP: Making CSP and Consent Work Together
Two Security Layers That Have to Agree
A Content Security Policy (CSP) tells the browser which sources a page may load scripts, styles, images, frames and network requests from. A consent management platform does the opposite job: it decides which of those scripts are allowed to run based on the user’s choice. When the two are configured separately, they collide. The CMP fails to load because CSP blocks it, or the CSP is loosened so much to accommodate the CMP and ad tags that it no longer protects anything.
This guide explains which CSP directives a CMP typically needs, how nonces interact with script injection, and how to roll out changes without breaking your banner.
What a CMP Usually Needs From CSP
Exact requirements depend on the vendor, so always start from your CMP’s own documentation. In general, check these directives:
script-src— the domain the CMP script loads from, plus any additional script files it loads.connect-src— endpoints the CMP calls withfetchor XHR, such as configuration, vendor list, geolocation and consent logging endpoints.style-src— the stylesheet domain, and possibly inline styles if the CMP injects a<style>element or sets style attributes.img-src— logos, icons and any tracking-free pixels the CMP uses.font-src— if the banner loads its own fonts.frame-src— if the CMP uses iframes, for example for cross-domain consent sharing.
On TCF sites, remember that the CMP also creates a locator frame named __tcfapiLocator on the page. That frame is created without a remote source, so it normally does not need a frame-src entry, but it is worth confirming in testing.
The Inline Stub Problem
Most CMPs ask you to place a small inline script — the stub — in the page head, so other scripts can queue calls before the main CMP loads. A strict CSP blocks inline scripts unless you allow them explicitly. You have three options:
- A nonce. Generate a random value per response, add it to the CSP header as
'nonce-...'and to the stub’s<script>tag. This is the most flexible option for dynamic pages. - A hash. Add the SHA-256 hash of the stub’s exact contents to
script-src. This works well for static sites, but any change to the stub, even whitespace, breaks it. - Self-hosting the stub as an external file on your own domain, covered by
'self'. This needs care to keep it loading early enough.
Avoid 'unsafe-inline' as a shortcut. It allows every inline script, which removes much of the protection CSP provides.
Nonces and Scripts the CMP Injects
A CMP that blocks scripts until consent often works by holding them back and injecting them later. Under a nonce-based CSP, those injected scripts need to be allowed too. There are two common approaches:
- Nonce propagation. The CMP reads the nonce from its own script tag and adds it to each script it injects. Check whether your CMP supports this.
'strict-dynamic'. This keyword tells supporting browsers to trust scripts loaded by an already-trusted script. It simplifies life with tag managers and CMPs that inject scripts, but it also means any script you trust can load others, so trust carefully.
Tag managers raise the same question. If you load ad and analytics tags through a tag manager, follow its documentation on nonces and make sure the CMP and tag manager use a consistent approach.
Ad Tags Make CSP Harder
Programmatic advertising loads scripts and frames from many domains, and the list changes as demand partners change. Publishers usually make one of two choices:
- A strict CSP on pages without programmatic ads, and a broader policy where ads run.
- A policy focused on directives that remain manageable, such as
frame-ancestors,object-src 'none'andbase-uri, while script sources stay broader.
Either way, the CMP should be on the explicit allow list, not covered only by a wildcard, so that a CSP change never silently disables the banner.
Roll Out With Report-Only Mode
Use the Content-Security-Policy-Report-Only header first. The browser reports violations without blocking anything, so you can see what a new policy would break.
- Deploy the policy in report-only mode with a reporting endpoint.
- Load pages in clean browser profiles and go through the full consent flow: first visit, accept all, reject all, custom choice, withdrawal.
- Review the violation reports and the browser console for blocked CMP resources.
- Adjust the policy, then switch to enforcement.
- Keep reporting enabled after enforcement to catch regressions.
Symptoms of a CSP Conflict
- The banner never appears, and the console shows a CSP violation for the CMP script.
- The banner appears but is unstyled, because
style-srcblocks its CSS. - Choices are not saved, because
connect-srcblocks the consent logging call. - Ads or analytics never load after consent, because injected scripts lack a nonce.
__tcfapiis undefined, because the inline stub was blocked.
Key Takeaways
- Check
script-src,connect-src,style-src,img-srcandframe-srcfor your CMP. - Allow the inline stub with a nonce or hash, not
'unsafe-inline'. - Make sure scripts injected after consent receive a nonce or are covered by
'strict-dynamic'. - Test every consent path in report-only mode before enforcing.
- Keep the CMP explicitly allow-listed so CSP changes cannot disable it.