There are many benefits to adopting CSP — from blocking attacks (such as XSS), to improving security posture and compliance. However, many developers that try to use CSP — lack the experience and tooling to do so effectively — creating polices that may offer lower protection, and worse — block legitimate parts of their web applications — breaking functionality in production!
So many developers get reports of their site getting broken — only to check and see this dreaded error:
**Refused to load the script**'...' **because it violates the following Content Security Policy directive** : "script-src 'self' 'report-sample'". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.
The below 10 tips, learned from years of deploying CSP at scale — will take you from A to Z with regards to proper deployment of Content-Security-Policy without breaking your website / web application.
We’ve baked these best practices into the RapidSec product — especially in the Security Manager that help you generate your CSP policies and maintain the versions over time. However, while some of the tips below refer to RapidSec’s features — since they are derived from an inherent need, you could very well use them independently.
Many developers start out directly setting the Content-Security-Policy http header - in a staging site, or even production! This approach has the undesirable side-effect of blocking legitimate assets that load in your site. The correct approach is to start out with a Content-Security-Policy-Report-Only Header, that merely warns of CSP violations, but does not block them in practice.
By using this learning policy first, you can start out with a very strict policy, and send violation reports to your report-uri endpoint - simulating an enforced policy, and helping to map-out the structure of your site in order to later create a powerful CSP.
Start your process with a report-only LEARNING policy, which is not actually blocking anything. Namely - at this step you WANT to actually create more CSP reports, used by a generator to create a CSP automatically.
An initial bare policy for mapping out your site will look something like this:
HEADER NAME: **content-security-policy-report-only** HEADER VALUE (prettified): **frame-ancestors**'self'; **block-all-mixed-content** ; **default-src**'self'; **script-src**'self' 'report-sample'; **style-src**'self' 'report-sample'; **object-src**'none'; **frame-src**'self'; **child-src**'self'; **img-src**'self'; **font-src**'self'; **connect-src**'self'; **manifest-src**'self'; **base-uri**'self'; **form-action**'self'; **media-src**'self'; **prefetch-src**'self'; **worker-src**'self'; **report-uri** [https://gate.rapidsec.net/g/r/csp/YOUR\_SPECIFIC\_PROJECT\_DETAILS](https://gate.rapidsec.net/g/r/csp/YOUR_SPECIFIC_PROJECT_DETAILS)
This policy will simulate blocking out all external assets loaded into the site, and send the reports to RapidSec (which will generate a CSP with this information). For stricter deployments, you can change 'self' (which is CSP for same-origin) with 'none' which will create more reports.
Some legacy tutorials suggest adding the CSP inside your htmlas a meta tag - like so:
The meta approach is counter-productive and has multiple drawbacks, including:
- The Content-Security-Policy-Report-Only header is not supported, so you would be breaking rule #1 of starting out in report-only.
- The CSP report-uri directive is not supported in meta, so you are flying blind basically.
- Other Important directives like frame-ancestors or sandbox are not supported in meta.
- The meta header CSP is generally unmaintained and causes many bugs in various browsers.
Some of the CSP errors that you may encounter when deploying your policy via meta tags:
The Content Security Policy directive 'report-uri' is **ignored when delivered via a <meta> element**. The report-only Content Security Policy 'default-src 'none'; script-src 'self'; report-uri /violations' **was delivered via a <meta> element, which is disallowed. The policy has been ignored**.
Solution: The preferred delivery mechanism for setting CSP (either enforced or report-only) is via HTTP headers. Btw — you can also set multiple CSPs, or one policy running in Report-Only, while another one is enforced. More on that, later.
Note: In some rare instances you cannot set HTTP header (some hosted platforms restrict this), at which — your only option would be to set a meta header if you wish to leverage CSP.
Without maintaining proper versions, it’s really hard to control your CSP policies at scale, and understanding which CSP, created which report. Think maintaining code without proper version control and accompanying platform like github!
Version control is critical with maintaining a CSP — and your setup should make sure that you know exactly which policy created which report.
Setting the headers dynamically can be quite challenging to maintain. Seeing this need, we created various plugins to support the needs of deployments to multiple environments.
- WordPress CSP & Security Headers — making it easy to deploy CSP in WordPress, and creating a setup to separate the front-site CSP from the wp-admin CSP
- RapidSec Node.js module — use your tokens to connect to your RapidSec project to Node.js easily, supporting multiple environments for Production, Staging, Dev — and allowing to easily push changes.
There is also the option of manually maintaining the Security headers deployment, while still maintaining version control in RapidSec. We allow to pull the CSP and other config via API, and push changes via Webhooks — features that many of our enterprise customers use to integrate into their CI/CD.
OK great — so you integrated you report-only CSP, and are now getting some report logs, only to discover that the majority of Content-Security-Policy reports sent to your CSP gateway, are actually not related to a part of your code / infrastructure, but rather generated by services used by your site. These 3rd party services can be impossible to reverse-engineer yourself — as some scripts have dynamic conditions that only get loaded for specific users.
Lets say for example that you use intercom for chat and support in your web application. If you only used your own reports to generate you CSP, it would take forever to discover that intercom actually requires DOZENS of custom directives to properly work with CSP.
That is why we invested many resources in creating crowdsourced CSP packages, that contain pre-built policies for the top 65 services on the internet. We’re also growing and actively maintaining this list.
RapidSec automatically detects the packages associated with you CSP reports, and the CSP Generator (Security Manager) will automatically suggest the needed policies. You just have to click “Allow”!
By easily clearing the noise and allowing known packages, you’re setting yourself up for success — leaving you to make decisions only on the actual custom code in your site.
This also saves you a lot of money on useless requests, as most CSP log service providers charge “per request” (and we also have a data-point limitation).
Some approaches suggest allowing highly custom CSP directives like:
img-src https://cdn.mysite.com/somefolder/hero_image_v5.png ...;
However, by explicitly allowing only a specific image - you would be forced to maintain a very large list - especially if this is a marketing site with widely changing content. In fact, our experience has shown this task to be especially hard!
Examples of use cases in which this scenario would cause the site to break, with the content-security-policy (CSP) blocking legitimate parts of the site:
- The marketer has changed the image to:
https://cdn.mysite.com/somefolder/hero_image_v6.png, but the CSP was not updated.
- There is a different view (say responsive) that was introduced:
https://cdn.mysite.com/somefolder/hero_image_wide.png, but this only gets rendered on very wide screens, so was not collected in initial configuration of the policy.
The best setting in this case would be to allow the entire CDN:
img-src https://cdn.mysite.com ...;
RapidSec CSP Generator, has warnings when you create rules that are too strict and may end up breaking your site, or create excessive logs from legacy browsers:
Not all websites are created equal! Think of implementing CSP on the below site types:
- Public content site — with dynamic injection of ad scripts. Requires a basic policy without using content directives or a default-src, with loosened enforcement.
- General marketing site — with a few 3rd party scripts / ad services. Requires a compact policy without enforcing the content directives or default-src.
- Web application requiring login — since these applications tend to have mission-critical data, or permissions — they required a strict policy with full enforcing of all directives.
Obviously there are more cases — some sites only use content-security-policy-report-only due to the varying nature of the site and content. Other sites we've seen using RapidSec (like payment gateways) - have such strong policies that they don't even allow external packages, with everything loading through the same-origin ('self' in CSP language).
Examples of how these setups make look in the RapidSec Security Manager, from the least strict, to the more strict.
If you have a mission critical site that you’re creating a CSP for — don’t worry!
Due to the architecture and lower dependency on 3rd parties in Web applications (rather than sites) — It’s actually easier to generate a strict content security policy for them without breaking the app, than it is to create a basic policy for a widely public site (thought that is also possible and effective with RapidSec).
So where does your project stand?
As mentioned above — in some cases we won’t enforce all the directives. For the directives that we do enforce — another great technique to avoid blocking legitimate content when enforcing your CSP is switching it to a less strict version of your reporting policy.
Say that your Report-Only policy has:
script-src 'report-sample' https://www.youtube.com https://m.youtube.com https://widget.cloudinary.com [https://upload-widget.cloudinary.com](https://upload-widget.cloudinary.com)
You could run the enforced CSP in a less strict mode by adding 'self', dropping the https:// protocols, consolidating the Youtube / Cloudinary directives to *.domain.com, and allowing unsafe-eval .
The policy might look something like this:
script-src 'self' 'report-sample' 'unsafe-eval' *.youtube.com *.cloudinary.com
This way — you are enjoying the best of both worlds. Still enforcing a great policy, while running the stricter version in Report-Only, connected to a report-uri that will collect the violations (and hopefully generate meaningful alerts when needed).
RapidSec makes this rollup of the directives seamless — see an example from the RapidSec Security Manager on the script-src directive:
We often see the case of developers being too eager to roll out an enforced policy — rolling it out enforced after having run only locally, or on real traffic in reporting mode for less than a day.
Don’t make this mistake — instead take your time!
You should have at least several days of pure reporting policy data on real traffic before combining enforcement.
Some things that get missed when moving to enforcement too soon:
- Geography specific features — for example eu subdomains of services - that load dynamically in some regions.
- User specific features — applications may expose some features only to certain users (behind feature flags), so they are less likely to show up in initial reporting data.
- Less frequently used features — Yes — you remember that legacy feature in your application that nobody uses, is written in a legacy technology, and gets loaded via iframe! Take the time to have users generate reports on this too.
Some developers waste days, or even weeks trying to tinker their Content Security Policy, while even testing half-baked enforced policies on production systems , blocking legitimate parts, and breaking mission critical functionality.
Instead, just seek expert advice, based on years of hands on experience — to swiftly overcome these initial challenges.
Our team at RapidSec is more than happy to help you build the best CSP without breaking your site — feel free to reach us here:
Book a RapidSec Demo — Learn how RapidSec manages CSP (and other security headers) end-to-end to meet your security posture goals easily.
Or just contact us if you have any questions.
There are some discussions on wether you should start with a nonce / hash based CSP or an allowlist based CSP - I believe that the Allowlist approach is the best way to get the major benefits of CSP!
Using nonce / strict-dynamic / hash requires major refactoring of your web application / site code, and in many cases changing serving infrastructure to support this setup (that does not jive well with cached sites).
Hence, it’s best to use an Allowlist policy , generated via the report-uri logs. For mission-critical assets with high XSS risk, add separate policies with nonce / hash / trusted-types .
CSP deployment can be easy, if you incorporate the correct measures and planning to ensure success. You can Generate your Contnet-Security-Policy with RapidSec and have the 10 above tips “Baked-in” to your flow, but if you are taking another route — this knowledge is still highly valuable for you.
Originally published at https://blog.rapidsec.com on October 22, 2021.