How to check CSP header
Read the header and split it on semicolons so each directive lands on its own line: curl -sI https://developer.mozilla.org/en-US/docs/Web/HTTP | tr ';' '\n'. Then answer two questions about what came back. Is the header enforcing or report-only, and does script-src contain unsafe-inline. Those two answers decide whether the policy stops anything.
Checker offline. Follow the manual steps below, they give the same answer.
Why check this
A Content-Security-Policy is the control that keeps an injected script from executing. Check it when the front end ships a new third-party tag, and again before a release turns a trial policy into the real one. The failure it prevents is a policy that has been in report-only mode since the pilot, so every violation is logged, nothing is blocked, and the team reads the header name in a scan report as proof of protection.
Prerequisites
- curl 8 or later. See the curl manual.
- Node 22 and Chrome for step 4 and step 5, which need a browser to execute the policy.
- The MDN reference on Content-Security-Policy for directive semantics.
- A local target. Save this as
csp-target.jsand runnode csp-target.jsbefore step 4.
const http = require('node:http');
const policy = "default-src 'self'";
const routes = {
'/enforce': { 'content-security-policy': policy },
'/report-only': { 'content-security-policy-report-only': policy },
'/unsafe-inline': { 'content-security-policy': policy + "; script-src 'self' 'unsafe-inline'" },
};
http.createServer((req, res) => {
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', ...routes[req.url] });
res.end(`<!doctype html><meta charset="utf-8"><title>csp target</title>
<p id="p">inline script did not run</p>
<script>document.getElementById('p').textContent = 'inline script ran';</script>`);
}).listen(8100, () => console.log('csp target on http://localhost:8100/'));
Steps
- Step 1.
Find out which of the two header names the response carries.
curl -sI https://developer.mozilla.org/en-US/docs/Web/HTTP | tr -d '\r' | grep -io '^content-security-policy[a-z-]*:'content-security-policy:A name ending in
-report-onlymeans the browser logs violations and blocks nothing. - Step 2.
Split the policy into one directive per line so it can be read.
curl -sI https://developer.mozilla.org/en-US/docs/Web/HTTP | tr -d '\r' | grep -i '^content-security-policy:' | tr ';' '\n' | sed 's/^ //'content-security-policy: default-src 'self' script-src 'report-sample' 'self' 'wasm-unsafe-eval' assets.codepen.io … script-src-elem 'report-sample' 'self' 'wasm-unsafe-eval' assets.codepen.io … style-src 'report-sample' 'self' 'unsafe-inline' transcend-cdn.com object-src 'none' base-uri 'self' connect-src 'self' developer.allizom.org bcd.developer.mozilla.org … font-src 'self' frame-src 'self' mdn.github.io *.mdnplay.dev jsfiddle.net codepen.io … img-src 'self' data: *.githubusercontent.com *.gravatar.com … manifest-src 'self' media-src 'self' archive.org videos.cdn.mozilla.net … child-src 'self' worker-src 'self'default-src 'self'is the fallback for every fetch directive the policy does not name. There is noframe-ancestorsline here, so this policy says nothing about framing. - Step 3.
Look for the two keywords that switch script protection off. Keep the quotes in the pattern.
curl -sI https://www.cloudflare.com/ | tr -d '\r' | grep -i '^content-security-policy:' | tr ';' '\n' | sed 's/^ //' | grep -E "'unsafe-inline'|'unsafe-eval'" | cut -c1-58script-src 'self' 'unsafe-inline' 'unsafe-eval' https://st… style-src 'self' 'unsafe-inline'The same command against
developer.mozilla.orgreturns only thestyle-srcline, so that policy still controls script execution. - Step 4.
Start the local target, open
http://localhost:8100/enforcein Chrome, and read the Console tab.http://localhost:8100/enforceExecuting inline script violates the following Content Security Policy directive 'default-src 'self''. … The action has been blocked.The paragraph on the page still reads
inline script did not run, which is the DOM proof that the block happened. - Step 5.
Open
http://localhost:8100/report-onlyand compare the last sentence of the same message.http://localhost:8100/report-onlyExecuting inline script violates the following Content Security Policy directive 'default-src 'self''. … The policy is report-only, so the violation has been logged but no further action has been taken.The paragraph now reads
inline script ran. Same policy text, same violation, opposite outcome. - Step 6.
Open
http://localhost:8100/unsafe-inline, where the policy adds that one keyword.http://localhost:8100/unsafe-inline(no console messages)No violation is reported at all. The inline script ran, and the policy raised nothing, which is what an injected inline script would also get.
How to read the result
| What you see | What it means | What to do |
| --- | --- | --- |
| content-security-policy: | The policy is enforced | Read the directives. Step 3 decides whether enforcement bites. |
| content-security-policy-report-only: | Violations are logged, nothing is blocked | Treat the page as having no CSP until the header is renamed. |
| Both header names present | The report-only copy is a candidate policy under trial | Normal during a rollout. Compare the two, and confirm which one ships. |
| 'unsafe-inline' in script-src | Any inline script on the page executes | Replace with a nonce or a hash. Until then the policy does not stop injection. |
| 'unsafe-eval' in script-src | eval and new Function are permitted | Find the library that needs it. This is usually one templating dependency. |
| No default-src and no script-src | Script loading is unrestricted | The policy covers only the directives it names. Everything else is open. |
Common mistakes
What to check next
- How to check security headers: the sweep that tells you whether this header is set at all on every route.
- How to check if a site is vulnerable to clickjacking: what
frame-ancestorsinside this same header controls. - How to check if mixed content exists on a page:
upgrade-insecure-requestsin a policy changes what mixed content does. - How to check subresource integrity: the hash check that pairs with a host allow list in
script-src. - Security headers checker: paste a URL and read the split policy without the shell pipeline.
FAQ
How to test content security policy?
Steps 4 to 6. A policy is code the browser runs, so reading the header is only half the check. Load the page in Chrome, trigger the behaviour the policy restricts, and read the Console tab for refusals.
How to check CSP header in Chrome?
Open DevTools, Network tab, reload, click the document request, then Headers and Response Headers. The Console tab lists each violation, and the Issues tab groups them by directive with the offending URL.
How to check if CSP is enabled?
Run step 1. An empty result means no policy on that response. A name ending in -report-only means the policy is present and enforces nothing.
How to check CSP violations?
Violations appear in the Chrome Console as refusal messages, and are posted as JSON to the endpoint in report-to or report-uri when the policy sets one. Without a reporting endpoint, only the browsers of people who hit the violation see it.
Does report-only mode protect anything?
No. Step 5 executed the inline script the enforcing policy blocked in step 4. Report-only exists to collect violations while a policy is tuned, and it is a rollout stage, not an outcome.
Verified
Verified by Maks Vernycurl 8.21.0node 22.23.2
Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.
Related on this site
- Checker: security-headers CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy
- Security headers review
- Website launch checklist
- All security headers and tls checks
intermediate9 minpublished updated Maks Verny