A Content Security Policy and a cookie consent banner are two separate controls that end up fighting over the same script tags. CSP is a security header that tells the browser which scripts are allowed to run, usually by tagging trusted ones with a nonce. Consent tooling does the opposite job: it stops tracking scripts from running until a visitor agrees. Put both on one page and the nonce that hardens your CSP becomes the reason your consent tool cannot switch scripts back on after someone clicks Accept.

The fix is not to weaken either one. It is to understand where they touch, and to configure them so the security layer and the privacy layer reinforce each other instead of cancelling out.

Does a Content Security Policy block cookies?

No. A Content Security Policy controls where scripts, styles, and other resources may load from, as a defence against cross-site scripting. It says nothing about whether an allowed script sets a cookie. A script that passes your CSP can still drop an advertising cookie the moment the page loads, before a visitor has agreed to anything.

The two mechanisms answer different questions. CSP asks whether a script comes from a source you trust. Consent tooling asks whether the user has agreed to that category of tracking. A script can be entirely trusted by your CSP and still be unlawful to run before consent, because the ePrivacy Directive, specifically Article 5(3), requires informed prior consent before non-essential cookies or similar storage are placed on a device. That obligation runs alongside GDPR, which governs the personal data those cookies then process. CSP has no view on either.

Because the two controls are owned by different people, the gap is easy to miss. Security teams ship a strict CSP; marketing teams ship a consent banner through a tag manager. Each assumes the other has tracking covered. Neither does.

QuestionContent Security PolicyConsent-based script blocking
Primary purposePrevent cross-site scripting and untrusted resource loadsPrevent tracking cookies before consent
What it checksScript source and integrity (nonce, hash, host)User consent state per category
Legal driverNone (a security best practice)ePrivacy Article 5(3), GDPR, national cookie law
Where it runsBrowser, from an HTTP header or meta tagUsually client-side JavaScript
Blocks a trusted tracker before consent?NoYes

Why does a nonce break consent-based script blocking?

A nonce breaks it because consent tools block a script by rewriting it, then re-inject a fresh copy once consent is given, and that fresh copy carries no nonce. Under a strict nonce-based CSP the browser refuses the re-injected script, so the tracker a visitor just approved never actually loads. The banner reports success; the analytics tag stays dark.

To see why, look at how the blocking works. Most consent tools conditionally load third-party scripts by neutralising the tag in the markup. The type is switched to text/plain so the browser treats the script as inert text, and the real URL is parked in a data-src attribute:

<script type="text/plain" data-consent="analytics"
        data-src="https://www.googletagmanager.com/gtag/js?id=G-XXXX"></script>

When the visitor accepts the analytics cookie categories, the tool finds every parked tag and builds a live script element from it:

const blocked = document.querySelector('script[data-consent="analytics"]');
const s = document.createElement('script');
s.src = blocked.dataset.src;
document.head.appendChild(s); // refused by a strict CSP: this element has no nonce

That new s element was never stamped with the per-request nonce, so the browser blocks it. The obvious workaround, reading the nonce off the original banner script and copying it across, does not work either. For security reasons browsers hide the nonce content attribute: element.getAttribute("nonce") returns an empty string. This stops attackers pulling the value out through a CSS attribute selector such as script[nonce~="abc"] { background: url(...) }. The nonce survives only on the element.nonce IDL property, which a tool has to read deliberately.

How does strict-dynamic fix the CSP and consent conflict?

Add a nonce to the consent tool's own loader script and put strict-dynamic in your script-src directive. The strict-dynamic keyword propagates the trust of a nonced script to any scripts it creates, so the tags your banner injects after consent run without needing a nonce of their own.

In practice the policy carries a fresh nonce on every response, and the loader tag carries the matching value:

Content-Security-Policy: script-src 'nonce-PER_REQUEST' 'strict-dynamic'; object-src 'none'; base-uri 'none'
<script src="https://cdn.kukie.io/loader.js" nonce="PER_REQUEST"></script>

Two properties of strict-dynamic are worth knowing before you rely on it. It ignores host-source allowlists in CSP level 3, so a policy cannot mix strict-dynamic with a list of trusted domains; the nonce plus propagation is the whole gate. It also lowers protection a little, because a trusted script that creates further scripts from tainted input can pull in anything. That trade is usually acceptable for a consent setup, where the alternative is a brittle chain of manually stamped nonces.

There are two viable ways to make injected scripts run under a strict CSP. Pick one and apply it consistently:

  • strict-dynamic: nonce the loader, add the keyword, and let trust flow to every script the loader injects. Lowest maintenance, and the approach most consent vendors document.

  • Manual nonce propagation: read the loader's value from element.nonce and set it on each script the tool creates, so every injected tag carries a valid nonce. More code, but it keeps the policy free of strict-dynamic.

What CSP directives does a consent banner need?

A consent banner needs its loader allowed under script-src, its injected styles allowed under style-src, and its consent-logging or configuration endpoint allowed under connect-src. If it renders blocked embeds as placeholders, those need frame-src. Miss any one and the banner renders broken, fails to record a choice, or leaves iframes blank.

One precedence rule causes most of the confusion. The specific directives script-src-elem and style-src-elem override their general counterparts, and there is no fallback: if you define style-src-elem, an allowance sitting in style-src is ignored for style elements. A banner that works under a simple style-src can break the day someone adds the more granular directive without listing the banner's source in it.

Inline event handlers are the other trap. A CSP with a nonce disables inline onclick handlers, which many banner templates use on their Accept and Reject buttons. Those handlers have to move into script that calls addEventListener, or be allowed with an unsafe-hashes source. Automatic script blocking has a related limit: a consent tool cannot classify an inline script that has no src, so under a strict CSP those inline blocks may slip through the blocking logic even while the CSP governs everything else. A platform such as Kukie.io that scans every script on the page can show which scripts set cookies and which category they belong to, which is what you need before writing either policy.

DirectiveWhat a consent banner needs in it
script-srcThe loader's nonce (or hash), plus strict-dynamic if scripts are injected after consent
style-src / style-src-elemThe banner's injected styles, via nonce, hash, or the banner's host
connect-srcThe consent CDN and any endpoint that stores consent logs or fetches config
frame-srcSources for embeds shown as placeholders until the visitor consents
img-srcAny icon or tracking-pixel host the banner or its logging uses

Can you use a Content Security Policy to enforce consent?

Yes. Because the CSP is set server-side in an HTTP header, you can serve a restrictive policy that omits third-party tracking origins until the consent cookie is present, then serve the full policy once it is. This blocks those trackers at the browser level even if your client-side consent script fails to load, which client-side blocking alone cannot promise.

Client-side blocking depends on JavaScript that can fail to load, be blocked by an extension, or lose a race against a tracker that fires first. A header-based policy does not. This is a belt-and-braces layer that most sites skip, and the enforcement record shows why they should not. In September 2025 the CNIL fined SHEIN EUR 150 million for placing advertising cookies the moment visitors arrived, before any interaction with the banner, and for continuing to place them after users clicked Reject all. The same round of decisions hit Google with EUR 325 million. A server-side CSP swap would not fix a broken banner, but it would have stopped the third-party origins from loading at all until consent existed.

Swapping the policy on the consent cookie is a few lines. In PHP the pattern reads the cookie and appends tracking hosts only when consent is present:

$hasConsent = isset($_COOKIE['kukie_consent'])
    && str_contains($_COOKIE['kukie_consent'], 'analytics');
$trackers = $hasConsent
    ? "https://www.googletagmanager.com https://connect.facebook.net"
    : "";
header("Content-Security-Policy: script-src 'self' 'nonce-$nonce' $trackers");

Keep three limits in mind. CSP cannot stop first-party cookies your own server sets, so it is no help against a session ID written in PHP. Browser support for the more granular directives varies, so test with Content-Security-Policy-Report-Only before enforcing. And a policy cannot read consent categories, remember a choice, or show a banner, so it is a complement to a consent manager and to a plan to block cookies before consent, not a replacement for either.

Nonce or hash: which suits a cookie banner?

Use a nonce when your pages are rendered per request by a server that can inject a fresh value each time, which covers most PHP, WordPress, and server-rendered stacks. Use a hash when your pages are static or served from a CDN where you cannot generate a per-request value, which is common on static-site hosts and some AI website builders.

The difference comes down to how the trust token is produced. A nonce has to be unguessable and regenerated on every response, so it needs server logic in the request path. A hash is the fingerprint of a specific inline script and stays valid as long as that script is byte-for-byte identical, which suits pages that are pre-built and cached. The cost of a hash is that it must be recalculated and redeployed whenever the inline script changes, including when a consent vendor updates its snippet.

AspectNonceHash
GeneratedFresh per HTTP response, server-sideOnce, from the script's contents
Best forServer-rendered pages (PHP, WordPress, SSR)Static and CDN-served pages, many static builders
Breaks whenReused across responses (must be unique)The inline script changes by a single byte
Works with strict-dynamicYesYes

Whichever token you pick, the interaction with consent is the same: the tag that loads your Google Tag Manager container or your Meta Pixel still has to be blocked until consent, and still has to inherit trust once it runs. Pairing a strict CSP with Google Consent Mode v2 and category-aware blocking is what keeps third-party cookies and any local storage written by third-party scripts from firing early.

Frequently Asked Questions

Does a Content Security Policy make a cookie banner unnecessary?

No. A CSP is a security control that restricts where scripts load from, while consent is a legal requirement about whether tracking may run at all. They solve different problems and neither replaces the other.

Why does my tracking script get blocked after a visitor accepts cookies?

The consent tool re-injects the script as a new element that never received the page nonce, so a strict nonce-based CSP refuses it. Adding strict-dynamic or propagating the nonce to the injected script fixes it.

Is strict-dynamic safe to use with a consent manager?

It is widely supported and the standard fix for dynamically injected scripts. It does lower cross-site scripting protection slightly and it ignores host allowlists, so weigh that against the cost of propagating nonces yourself.

Can a Content Security Policy stop cookies before consent on its own?

Partly. A server-side policy that omits third-party tracking origins until the consent cookie is present will block those trackers, but it cannot stop first-party cookies your own server sets and it cannot show a banner.

Which CSP directive controls the cookie banner's styles?

style-src, unless you also define style-src-elem. The more specific directive takes precedence for style elements and there is no fallback, so the banner's style source must be listed there too.

Do I need to add my consent CDN to connect-src?

Yes, if the banner sends consent logs or fetches its configuration over the network. Those requests count as connections, so without the CDN in connect-src the browser blocks them and consent may not be recorded.

Check What Your CSP and Consent Setup Actually Do

If a strict Content Security Policy sits in front of your site, test that approved scripts still load after consent and that trackers stay blocked before it. Kukie.io scans the scripts on your pages, shows which ones set cookies, and works with nonce and strict-dynamic setups so your banner and your security header stop pulling in opposite directions.

Start Free - Scan Your Website