Content Security Policy (CSP): Setup, Bypass Risks, and Deployment

CSP (Content Security Policy) is a response header that tells the browser which scripts can run and which domains can load resources. Roll it out in three steps: run Report-Only for 14 days to collect violation sources, then tighten the allowlist,

CSP部署:报告模式先跑两周再切强制CSP: report-only for two weeks, then enforce 一上来就enforce会把站内第三方脚本全打挂Enforcing on day one breaks every third-party script 1 Report-Only1 Report-Only 只收集违规 不拦截Collect violations, no block 跑14天 收集所有来源Run 14 days to collect sources 2 白名单收敛2 Whitelist tuning 按报告加域名 不violent-inlineAdd domains from reports sha256 hash 替代unsafe-inlinesha256 instead of unsafe-inline 3 Enforce3 Enforce 违规日志为0再切Switch when reports hit 0 保留report-uri 继续监控Keep report-uri watching 核心指令Core directives default-src 兜底 script-src 控脚本default-src fallback, script-src controls JS object-src ‘none’ frame-ancestors 防嵌套object-src none, frame-ancestors 红线Red lines 别开 * 通配符 等于没配Wildcard * equals no CSP unsafe-inline 留着等于被绕过unsafe-inline defeats the purpose 判定:报告模式跑满14天且违规数降到个位数,再切EnforceDecision: enforce after 14 days of near-zero reports 反直觉:CSP不是越严越安全,配错了比不配更危险Counterintuitive: misconfigured CSP is worse than none 先报告后强制,把违规源一个个收进白名单Report first, enforce later
Deploy CSP in three steps: Report-Only collection, allowlist tightening, Enforce

Content Security Policy (CSP): Setup, Bypass Risks, and Deployment

Bottom line first: CSP (Content Security Policy) is a response header that tells the browser which scripts can run and which domains can load resources. Roll it out in three steps: run Report-Only for 14 days to collect violation sources, then tighten the allowlist, and only switch to Enforce once violation logs drop to single digits. One counterintuitive point: a misconfigured CSP is more dangerous than none at all. Enabling unsafe-inline means the wall was never built.

1. What CSP Actually Blocks

CSP delivers rules through an HTTP response header, and the browser uses them to decide which resources can load. It mainly guards against two problems: malicious scripts injected via XSS, and unauthorized third-party domains embedded in the page.

It is not a firewall. CSP cannot stop SQL injection, nor can it stop a server from being compromised. It governs resource loading on the browser side. It works alongside HTTPS and HSTS. For HTTPS setup, see HTTPS Setup for Your Own Site.

A common pain point for independent sites is too many third-party scripts: GA4, GTM, Hotjar, live chat, ad pixels. Every script you add means another domain on the CSP allowlist, or the browser blocks it. For third-party script governance, see Third-Party Script Governance for Independent Sites.

One often overlooked fact: CSP has almost no negative impact on SEO. Googlebot itself does not enforce resource restrictions beyond JS, so a properly configured CSP will not affect indexing. Worrying that CSP hurts crawling is unnecessary.

Directive What it governs Recommended value
default-src Fallback for all resources ‘self’
script-src JS script sources ‘self’ + analytics domains
style-src CSS sources ‘self’ ‘unsafe-inline’
img-src Image sources ‘self’ data: + CDN
object-src Plugins ‘none’ (must be disabled)
frame-ancestors Who can embed this site ‘none’

2. Three-Step Deployment: Report, Tighten, Enforce

The most common mistake is going straight to the Content-Security-Policy header. That immediately blocks every script on the site not on the allowlist, taking down GA4, live chat, and pixels all at once, and the business team will complain right away.

The right approach is to start with the Content-Security-Policy-Report-Only header. The browser only reports violations without blocking, so you collect two weeks of logs and add every domain actually in use to the allowlist.

Example scenario 1: adding CSP to a WordPress site, start with Report-Only.

Two days later, the report shows three domains: googletagmanager.com, hotjar.com, and youtube.com.

Add them one by one to script-src and frame-src. After two weeks, the report shows only single digits per day, so switch to enforce mode.

Stage Response header Action Duration
Step one Content-Security-Policy-Report-Only Report only, don’t block 14 days
Step two Same as above, add to the allowlist gradually Violations drop day by day Until you’re down to single digits
Step three Content-Security-Policy Actually blocking Long term
Throughout Add report-to or report-uri Collect violation reports Leave it on

Inline scripts are the biggest trap. WordPress themes and many plugins write <script> directly into the page, and CSP blocks inline by default. Two ways around it: pull the inline script out into an external .js file, or allow that specific block with a sha256 hash instead of taking the easy route and turning on unsafe-inline.

style-src can be looser than script-src. The Gutenberg editor and many themes output inline styles, so leaving unsafe-inline on style-src temporarily is not a big deal.

But script-src must never allow unsafe-inline. That is the core defense of CSP against XSS. Treat the two directives with different levels of strictness.

3. Bypass risks: don’t let CSP become a dead letter

A misconfigured CSP is as good as no CSP. These three mistakes will take the wall down entirely.

The first is turning on unsafe-inline. It lets any inline script run, including malicious scripts injected by XSS, and the core defense of CSP is broken. The second is using the wildcard *, which means no source restriction at all. The third is leaving out object-src, which lets older browsers bypass CSP through Flash or plugins.

Example scenario 2: taking the easy route and writing ‘unsafe-inline’ in script-src.

Two months later an inline phishing script gets injected into the site, and CSP doesn’t stop it at all.

The right approach is a nonce: generate a one-time random value per request and only allow inline scripts carrying it.

Wrong approach Risk Alternative
script-src * Any third-party script can load List allowlisted domains one by one
script-src ‘unsafe-inline’ XSS-injected scripts still run Extract to external files or use nonce/hash
Omitting object-src Old plugins can be abused object-src ‘none’
Not configuring frame-ancestors Pages get nested in malicious iframes frame-ancestors ‘none’

CSP and compliance are two different things. GDPR cookie consent is a separate mechanism, see GDPR Compliance for Your Own Site. CSP guards against script injection; it does not solve cookie consent. For server-level login and file permissions, see Security Hardening for Your Own Site.

Nonce or hash? Use a hash for fixed inline scripts and a nonce for dynamically generated ones. A fixed tracking snippet in a WordPress theme gets a hash; a script block whose content changes on every request gets a nonce.

Where you deploy it matters too. The CSP response header is best sent from Nginx or the CDN layer, not output by the PHP application. Application-layer output is lost when PHP fails; CDN-layer delivery stays up.

Deployment location Reliability Use cases
Nginx config High, survives app failure Self-hosted server
CDN rules High, takes effect globally If you use Cloudflare or similar
PHP application output Low, lost on failure Temporary testing

The report endpoint also needs its own setup. The address report-uri points to must not be blocked by CSP itself, or the reports won’t get through.

For a standalone site, you can use an off-the-shelf reporting service, or set up a dedicated log endpoint in Nginx to receive reports and write them to a separate log file, keeping them out of your main traffic logs.

Once report volume grows, you need to aggregate. You might get hundreds of reports a day, too many to read one by one, so group them by violated directive and blocked domain, and pay close attention to domains that show up for the first time each day.

A common misconception: people think once CSP is on, they’re all set. In reality, every new third-party script you add and every theme update can introduce new violating sources.

So after switching to Enforce, don’t turn off report-uri. It’s your only signal for catching “a newly added script that someone forgot to whitelist.”

Browser support is worth mentioning too. Modern browsers (recent versions of Chrome, Edge, Firefox, and Safari from the past two years) fully support CSP 3. You can ignore old IE entirely, since visitors to a B2B standalone site basically never use it.

One more deployment detail: the CSP header needs to be sent over both http and https. If you only send it over https, requests during the http origin-fetch stage won’t be protected by CSP.

After switching to Enforce, it’s a good idea to keep a Report-Only header running in parallel for a while, so you both block and report, and if you missed anything you can still see it in the reports.

CSP isn’t a one-time project. Every time you add a new third-party script (support chat, live chat, video, ads), first watch for it in report mode to confirm it shows up as a violation, then add the corresponding domain to the whitelist, and only then switch to production.

Add CSP checks to your new script launch checklist: whitelist the script at the same time you add it, rather than waiting until users report “the widget isn’t showing” after launch to remember CSP.

One rule of thumb: if a third-party script can’t be converted into an external file and its hash can’t be computed, it’s time to reconsider whether that third party is worth using. A third-party service that depends on unsafe-inline is technical debt in itself.

After deployment, spot-check page by page using the Security panel in your browser console. Go through the homepage, product pages, blog pages, and form pages, and confirm there are no red violation warnings.

For pages that fail the spot check, go back to the reports, find the corresponding domain, add it to the whitelist, and observe for another day. Repeat this cycle two or three times and you’re set.

CSP isn’t a security item that’s done once you deploy it; it evolves over the long term alongside your third-party script library. Every time you add a new script, update the whitelist in sync, and never miss whitelisting any new third-party script domain that appears.

Report source Meaning How to handle it
script-src violation New script not whitelisted Add the domain or extract to an external file
img-src violation Image hotlink from a new domain Add the CDN or switch to a relative path
frame-src violation A new iframe was embedded Add the video domain or remove it
base-uri violation Page was injected with a base tag Investigate XSS immediately

FAQ

Should CSP start in enforce mode from day one?
Don’t. Start with Content-Security-Policy-Report-Only in report mode for 14 days, so the browser only reports violations without blocking. Add each real domain that shows up in the reports to your allowlist one by one, and once violations drop to single digits, switch to Content-Security-Policy enforce mode. Going straight to enforce will break GA4, live chat, and ad pixels all at once.
Inline scripts in my WordPress theme are blocked by CSP. What do I do?
Don’t take the easy way out with unsafe-inline. There are two correct approaches: first, move the inline scripts into separate .js files and load them with src; second, for inline scripts you must keep, compute a sha256 hash and allow that specific hash in script-src. unsafe-inline lets malicious scripts injected via XSS run too, which defeats the whole point of CSP.
How does CSP relate to HTTPS and HSTS?
They work together but cover different things. HTTPS handles transport encryption, HSTS forces browsers to use HTTPS only, and CSP controls which scripts and resources the browser can load. HTTPS is the prerequisite; CSP adds XSS and unauthorized third-party script protection on top of HTTPS. Running CSP without HTTPS is pointless.
Why are all my CSP reports violations?
It means your allowlist is incomplete. Each violation report tells you which domain the blocked script came from, so add that domain to the relevant directive (script-src, img-src, frame-src, etc.). Third-party analytics, chat widgets, and video embeds are the most common ones people miss. That’s exactly the point of report mode: collect everything first, then enforce.
Can CSP stop SQL injection?
No. CSP runs in the browser and only controls resource loading, so it can’t stop server-side SQL injection. SQL injection has to be handled with prepared statements and input filtering. CSP mainly defends against XSS: malicious scripts an attacker injects get blocked by CSP and never execute, but the injection itself has already happened on the server.
Do I still need to keep report-uri?
Yes. Even after switching to Enforce mode, keep report-to or report-uri to keep collecting new violations. Newly added third-party scripts and inline code introduced by theme updates will show up in reports first. I’d recommend sending violation reports to a logging service and checking for unusual domains once a day.

Final thoughts

The hard part of CSP isn’t writing the rules, it’s having the patience to run report mode. Two weeks of observation buys you years of script control. That’s a trade worth making.

Three steps to get it done: first, ship Report-Only for 14 days; then add each real domain from the reports to your allowlist one by one and replace unsafe-inline with hashes; once violations drop to single digits, switch to enforce while keeping reporting on for ongoing monitoring.

One line worth remembering: your CSP’s security level depends on how clean your allowlist is. Leave unsafe-inline and wildcards in place and you might as well not have built the wall.

Crater
Crater Crater experts

Official Organization · Crater

Crater is a go-global services company specializing in WordPress standalone site development + Google SEO + GEO full-funnel growth, serving sectors including smart hardware, industrial equipment, SaaS, and new energy.

Product CapabilitiesMethodology WhitepaperIndustry Report
965articles shared
78knowledge base articles