---
title: "Introducing the Dynamic Content Security Policy (CSP) Integration"
description: "Protect against common web exploits like cross-site scripting (XSS) attacks with this CSP integration. Avoid maintaining a static allowlist of domains!"
source: "https://www.netlify.com/blog/general-availability-content-security-policy-csp-nonce-integration/"
last_updated: "2026-07-14T01:14:30.000Z"
---
> 💡 This blog post shows a dynamic approach of applying a Content Security Policy for the `script-src` directive. Other directives like `connect-src` would not benefit from this dynamic approach.

## The importance of a Content Security Policy

The web is open by default: any website can fetch scripts, stylesheets, images, fonts, and more as a resource from any domain. However, that leaves the door open for malicious actors to execute scripts on your site and attack your visitors. **Website owners who value security of their websites, as well as their customers who use them, are encouraged to increase the security posture of the frontend of their website(s) by implementing a Content Security Policy.**

### What is a Content Security Policy (CSP)?

A _Content Security Policy_ (CSP) is [“a mechanism by which web developers can control the resources which a particular page can fetch or execute”](https://www.w3.org/TR/CSP3/). It instructs the browser to **restrict network requests to a set of trusted domains** specified by the website. It is an effective tool in the security tool belt that helps prevent resources — scripts, stylesheets, images, etc — from loading or executing without the website owner’s consent.

![](https://cdn.sanity.io/images/o0o2tn5x/production/3ffb8c676dde02bf1cc35a880cddd6c34136b758-1600x809.png?auto=format)

When we refer to a CSP, we are talking about a website’s `Content-Security-Policy` response header. The header’s value is a string of text, a semicolon-separated list of **directives** (like `script-src`, `image-src`, `connect-src`, etc) and their _source lists_, a list of domains (or content hashes) separated by spaces. This response header controls which external resources are allowed to be accessed by the site.

**A CSP should be considered by any organization** concerned about client-side attacks, compliance, exfiltration of customer data, and website defacement.

 Sorry, your browser doesn't support embedded videos.

## Setting up a CSP is difficult to get right

The problem with CSP is that it is notoriously fragile as well as tedious to maintain. Here are some common pitfalls you may encounter when specifying an allowlist of domains or content hashes that your website’s frontend is allowed to talk to:

-   **The CSP may block legitimate resources from loading or executing.** If you forget to include a domain on this list, and your code legitimately attempts to fetch from this domain, then that request will be blocked.
-   **Inline snippets will need their content hashes recomputed if they change.** If you employ the `script-src` directive without `'unsafe-inline'`, then [inline scripts won’t execute](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#unsafe_inline_script) unless you also include a SHA of its content in the CSP as well. You’ll need to either manually hardcode this hash (and adjust as needed whenever the script changes) or write a build step that computes and injects this hash for you.
-   **Traditionally, CSP response headers are static, not dynamic, when served from a CDN.** This means you’d need to maintain an allowlist of every single domain referenced by your site, which can easily balloon up to tens or hundreds of domains. But don’t worry - we have a better solution. Skip down to [_Simplifying CSP with dynamically-generated nonces_](#simplifying-csp-with-dynamically-generated-nonces) for a spoiler on how we can implement a dynamic nonce.

In an effort to ease the operational risk of making CSP changes, a common strategy is to first deploy a `Content-Security-Policy-Report-Only` (CSP-RO) response header. This essentially sets up a dry-run of the CSP, reporting any _violations_ (resources that the browser would block due to the domain not being on the allowlist) to the URI specified in the CSP’s `report-uri` directive. With a CSP-RO in place, you can test your CSP candidate against real production traffic. However, this strategy isn’t foolproof:

-   **Only domains that users access during this test period will be reported.** Dusty corners of your app or site won’t be reported unless traffic is sent to them. Resources may point to different top-level domains (TLDs) based on the region the traffic is coming from (for example, `.de` domains may be accessed from Germany, whereas traffic from France might reach out to `.fr` domains), so if not all regions are active, then there will likely be holes in the reporting.
-   **The test period is ongoing and it does not end.** Typically, a CSP-RO is deployed alongside a CSP — acting kind of like a beta — where developers can test changes against production traffic to see if any violations are reported. If no violations are reported after some time, then the new rules are promoted to the CSP. The ongoing nature of CSP-RO is exhausting from a maintenance perspective.

Consider a marketing team that owns the Segment or Google Tag Manager implementation of a site. They want to add a script so that data starts flowing to this new destination. Unless this new destination’s domain is already included in the CSP, **it won’t start working until developers make a code change to add it**. This creates a bottleneck of marketers waiting on developers, as the code change will require human approval in a pull request.

As you can imagine, this allowlist would grow to become rather long. Take adding Google Analytics to the `connect-src` directive, for example: [Google itself recommends](https://developers.google.com/tag-platform/tag-manager/csp#google_analytics_4_google_analytics) that “each Google top-level domain (TLD) must be specified individually, since CSP syntax does not allow the use of wildcards on the right side of the hostname.” This number of [Google domains](https://www.google.com/supported_domains) is incredibly long (187 at time of writing!), and is what they recommend just to support a single service. Every external resource that the website reaches out to must be captured and documented, otherwise they will not load — it is strict by design. As a result, developers tend to be afraid to remove domains from this list, **so it grows to be an append-only list of rules** (much like a global stylesheet!). On top of that, this long allowlist is **included in every response** when served as a static header, inflating total request size and egress.

Many companies try to deploy a CSP, but never make it past the CSP-RO stage — including us! We had tried to enforce a CSP on the [Netlify platform](https://app.netlify.com), but it never graduated from `Report-Only`, because 1) **the workflow to approve new domains to the allowlist was too cumbersome**, and 2) **we weren’t confident that we were capturing all allowed domains in our proposed allowlist.**

[![On-demand webinar](https://no-cache.hubspot.com/cta/default/7477936/interactive-131420463505.png)](https://cta-service-cms2.hubspot.com/web-interactives/public/v1/track/redirect?encryptedPayload=AVxigLJBnTBd56nhxv6VOlQSa4tTK%2FHduA5UfRXLIn8mbwDNr6DHJkVyVE2GD7nRGEP7HcNiznFEy8C%2BindnuOscVbeqsvj75gWH4RuOsX4UWlh1D%2BuPOWHAUlwZpZYFyZN8QdC3UB75TU06BKDyN9gKqeqG7ahrYhEKOEAuTMKVRcJH4k%2F2rrq3V1JehT65d0V0HLye2ZyM%2BWqz5M61RhEYAcFKbFVfS7V%2FxVW7eKPRprbwzsRzn90Pxm301p1cONHYjipNdQU%3D&webInteractiveContentId=131420463505&portalId=7477936)

## Subresource integrity solves a different problem

You may have heard of [subresource integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) (SRI). Script tags include the `integrity` attribute, whose value must match the hashed value of the resource’s content, otherwise the browser will refuse to execute it. The hash for subresource integrity is static/deterministic; that is to say, the value does not change unless the content changes.

**But implementing SRI is only helpful if you are hotloading scripts from a third-party.** You want to be sure that the content you are downloading — such as a versioned library — does not change (either maliciously or accidentally). You also must calculate the sha ahead of time, and hardcode that value into your HTML, for it to become effective.

**What SRI does not do is prevent cross-site scripting attacks (XSS).** If you have user-generated content on your site, or collect user input from forms or query parameters, then your frontend developers must go through extra lengths to ensure that a maliciously-crafted input wouldn’t allow for scripts to be injected on the page. Modern frameworks like React have guardrails in place to prevent this, but there are some use cases (notably rendering markdown into HTML) where unsanitized markup can find a way to the client.

## Simplifying CSP with dynamically-generated nonces

The crux of the difficulty behind implementing a CSP successfully is that manually maintaining an allowlist is tedious. What if instead of maintaining an allowlist, we could guarantee integrity dynamically?

That’s exactly what a [CSP nonce](https://content-security-policy.com/nonce/) does: on every request, a randomly-generated cryptographically-secure string gets included in the CSP’s `script-src` directive. If the random string from the CSP header does not equal the value in a `<script>` tag’s `nonce` attribute, then the browser will refuse to execute that script. Scripts that have a matching `nonce` attribute are considered to be trusted, and when the [strict-dynamic](https://content-security-policy.com/strict-dynamic/) keyword is specified, then any script(s) created from the trusted script will also be trusted and allowed to execute.

## Using Netlify Edge Functions for dynamic CSP

Traditionally, CDNs aren’t able to dynamically generate response headers. The headers — like the content of the files themselves — are static, unless your site uses server-side rendering (SSR).

However, we have [Netlify Edge Functions](https://docs.netlify.com/edge-functions/overview/) in our arsenal, which act like a middleware layer for the CDN. An edge function can intercept requests that match certain criteria (in this case, HTML documents), add a response header, and transform `<script>` tags to include a nonce attribute that is _unique to every request_.

By providing the `script-src` directive with a dynamically-generated nonce at request-time, attackers won’t be able to inject their own client-side scripts. Even data exfiltration — usually protected by the `connect-src` directive — becomes much more difficult.

### How to implement dynamically-generated nonces on your Netlify sites

We’ve developed an extension using the [Netlify SDK](https://sdk.netlify.com) that will configure and deploy this edge function for you, called the [Content Security Policy extension](https://app.netlify.com/extensions/content-security-policy). Simply head to the **Extensions** page for your team, search for “Content Security Policy”, and install the extension:

![](https://cdn.sanity.io/images/o0o2tn5x/production/6303deeea99513e233471f0520ea1d8d82a9a033-1845x1122.png?auto=format)

After you install the extension on your team, navigate to **Site configuration > Build & Deploy > Content Security Policy** for the site you want to add a CSP nonce to and select **Enable**:

![](https://cdn.sanity.io/images/o0o2tn5x/production/5d7afd3fd9739b32d3a52aabcaca10e8af348b35-2970x984.png?auto=format)

The page will update and you can then configure the options in the same section:

![](https://cdn.sanity.io/images/o0o2tn5x/production/18c1c26f98177cac76d1810af7402793bd1defe4-2957x1446.png?auto=format)

Your next deploy will contain the CSP nonce. **If your site already has a CSP in place, this extension will merge the nonce with your existing directives.**

> 🧩 If you prefer configuration as code, you can also enable this functionality by installing the [@netlify/plugin-csp-nonce](https://www.npmjs.com/package/@netlify/plugin-csp-nonce) build plugin.

### Testing your dynamic CSP

You can test in a Deploy Preview before going live by clicking the **Test on Deploy Preview** button under **Site configuration > Build & Deploy > Content Security Policy**. And because all deploys on Netlify are [atomic](https://jamstack.org/glossary/atomic/) and [immutable](https://jamstack.org/glossary/immutable/), you can [instantly rollback](https://www.netlify.com/blog/2021/07/12/how-to-rollback-a-deploy-in-2-seconds-on-netlify/) if anything goes awry. Inspect your response headers, and you should see a CSP with a value like this:

`script-src 'nonce-6RIGit1eDC3GY0BRqEifv/dQ3OyKCZ8w' 'strict-dynamic' 'unsafe-inline' 'unsafe-eval' 'self' https: http:; report-uri /.netlify/functions/__csp-violations`

Here, the `'unsafe-inline'`, `'self'`, `https:`, and `http:` sources are supplied for [backwards-compatibility purposes](https://content-security-policy.com/strict-dynamic/), and are ignored by browsers that support the strict-dynamic source list keyword. Modern browsers support `strict-dynamic` (CSP Level 3 in Chrome 52+, Edge 79+, Firefox 52+, and Safari 15.4+).

By default, the extension will use the `Content-Security-Policy-Report-Only` response header for your CSP. This means that violations will be logged, but not blocked, giving you insight into how production traffic behaves with the policy without breaking functionality. Violations will be reported to and logged by a [Netlify Function](https://www.netlify.com/platform/core/functions/) also deployed by the extension — you can monitor the violation reports by checking the logs of the `__csp-violations` function.

To graduate from a `Content-Security-Policy-Report-Only` header to a `Content-Security-Policy` header, remove the `'unsafe-eval'` source list keyword (present by default for easier adoption), or configure a custom `report-uri`, just change the [configuration values](https://www.npmjs.com/package/@netlify/plugin-csp-nonce#configuration-options) for the extension under **Site configuration > Build & Deploy > Content Security Policy**.

**To gradually control the rollout of the nonce in your CSP** while you monitor violation reports, you can set the `CSP_NONCE_DISTRIBUTION` environment variable to a value between 0 and 1. This value controls the percentage of traffic that would receive a transformed response containing the CSP nonce.

The edge function that transforms your site’s HTML to include the CSP nonce runs after [snippet injection](https://docs.netlify.com/site-deploys/post-processing/snippet-injection/#app), so any user-defined snippets, as well as scripts supporting [Real User Metrics (RUM)](https://docs.netlify.com/monitor-sites/real-user-metrics/#app) and the [Netlify Drawer](https://docs.netlify.com/site-deploys/collaborate-on-deploys/#netlify-drawer-overview), will include the `nonce` attribute and won’t be blocked.

We’re so confident that this solution will help you implement a Content Security Policy for your site, that we’re putting our money where our mouth is: **We currently use this edge function ourselves on 100% of requests to the** [**Netlify platform**](https://app.netlify.com/). Inspect the response headers of the Netlify UI to see for yourself! We’ve been living with this in production for months now, and haven’t seen delays in response time or other ill effects. If Netlify Edge Functions start having a degraded or unavailable service, the [edge function will by bypassed](https://docs.netlify.com/edge-functions/optional-configuration/#configure-error-handling-for-an-edge-function), meaning that the request chain will continue (albeit without the CSP nonce in place) rather than crashing altogether.

![](https://cdn.sanity.io/images/o0o2tn5x/production/ba06457240d89e1feb324780c83513abcd928440-1600x188.png?auto=format) _The dynamic nonce gets added to both external and inline scripts._

> 💡 Don’t forget to test your updated CSP against common browser extensions that your users use. We’ve tested and confirmed that the Google Lighthouse, Google Translate, and 1Password browser extensions all work when the extension is enabled.

## Enhanced web security made simple with the Netlify Dynamic CSP extension

We’re excited for you to try out the [Content Security Policy extension](https://app.netlify.com/extensions/content-security-policy) on your site. [Let us know what you think](https://answers.netlify.com/t/automate-your-content-security-policy-csp-with-our-new-integration) in the forums! If you or your team encounters any issues, please let us know by filing an issue on the [GitHub repo](https://github.com/netlify/integration-csp).

If you’re not a Netlify Enterprise customer, reach out to our team for a [custom demo](https://www.netlify.com/contact/).

### Share

-   [X (fka Twitter)](https://twitter.com/intent/tweet?text=How I learned to stop worrying and love the Content Security Policy&url=https://www.netlify.com/blog/general-availability-content-security-policy-csp-nonce-integration/)
-   [LinkedIn](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fwww.netlify.com%2Fblog%2Fgeneral-availability-content-security-policy-csp-nonce-integration%2F)
-   [Facebook](https://www.facebook.com/sharer.php?u=https://www.netlify.com/blog/general-availability-content-security-policy-csp-nonce-integration/)
-   [Bluesky](https://bsky.app/intent/compose?text=How I learned to stop worrying and love the Content Security Policy+https://www.netlify.com/blog/general-availability-content-security-policy-csp-nonce-integration/)

* * *

### Tags

-   [Security](/blog/tags/security/)
-   [changelog](/blog/tags/changelog/)

## Keep reading

![](/_astro/cad30e363e211e560dfdb653a85ab426d48f68fe-2400x1260_ZrhAo6.webp)

Opinions & Insights August 21, 2023

[

### Get ready to Ship \*\*it Securely

](/blog/ship-it-securely-with-netlifys-enterprise-security-infrastructure)

-   ![Profile picture of Mark Dorsi](/_astro/98c49c1fbc9a6a3cdf0528a6d9194a69fd35dea8-1367x1324_Z2VJaw.webp)
    
    Mark Dorsi
    
-   ![Profile picture of Cat Allen](/_astro/5f5b62f8be63dbef6d68ddbd392be73ce149202e-512x512_jerfz.webp)
    
    Cat Allen
    

![](/_astro/26e66e751004c7445ceaca97de8a1db5cd757ba7-2400x1260_24PH06.webp)

News & Announcements August 22, 2023

[

### Introducing Netlify Secrets Controller: Proactive security for secret keys

](/blog/general-availability-secrets-controller)

-   ![Profile picture of Sean Roberts](/_astro/bbf2243f8171dbddd80ab2103622106cef84d125-512x512_Z1d2LKE.webp)
    
    Sean Roberts
    

## Recent posts

News & Announcements July 13, 2026

[

### Netlify inside of Claude Design

](/blog/netlify-inside-of-claude-design)

-   ![Profile picture of Sean Roberts](/_astro/bbf2243f8171dbddd80ab2103622106cef84d125-512x512_Z1d2LKE.webp)
    
    Sean Roberts
    

News & Announcements June 25, 2026

[

### Netlify Functions, designed for Agent Experience

](/blog/netlify-functions-designed-for-agent-experience)

-   ![Profile picture of Eduardo Bouças](/_astro/52958f21e8450baf6d8e60302341a984e220c0cd-512x512_13VDlu.webp)
    
    Eduardo Bouças
    

News & Announcements June 24, 2026

[

### How we measure Netlify’s Agent Experience

](/blog/how-we-measure-netlify-agent-experience)

-   ![Profile picture of Sean Roberts](/_astro/bbf2243f8171dbddd80ab2103622106cef84d125-512x512_Z1d2LKE.webp)
    
    Sean Roberts
    

![](/_astro/3f255b372fa958df35802666ee33b4609b2d71bd-1200x1586_1VtE2D.webp)

### How do the best dev and marketing teams work together?

[Access the report](https://www.netlify.com/reports/2024-leadership-trend-report/access/)