CSP header checker API: grade a Content-Security-Policy and see what it blocks
A Content-Security-Policy that looks fine can still be useless: 'unsafe-inline' without a nonce, a bare self that browsers read as a host name, a wildcard script source, or frame-ancestors tucked into a <meta> tag where it is ignored. Paste the header your server sends and this endpoint reads it the way a modern browser does, grades it, and optionally answers "would this script / fetch / frame be allowed?".
/v1/csp/parse?header=default-src%20%27self%27%3B%20script-src%20%27self%27%20%27unsafe-inline%27&page=https%3A//app.example.com/&check=inline-script&code=alert%281%29 returns JSON. No key, CORS on, 60 requests a minute./v1/csp/parse
CSP header explainer: grade a Content-Security-Policy and check what it blocks. Change the values and press Send request to call the live API.
Parameters
| Name | Type | What it does | Default |
|---|---|---|---|
header | string | A Content-Security-Policy value, a header dump (with or without the name), or a <meta> tag. Up to 8 KB. | — |
page | string | Document URL used by the optional check simulator (scheme+host+port) | — |
check | string | Optional action to test: script, inline-script, attr, eval, style, inline-style, style-attr, img, font, connect, frame, media, worker, object, form, base, ancestor | — |
url | string | Resource / form / ancestor URL for the check (not needed for eval or inline kinds) | — |
nonce | string | Nonce value to try against 'nonce-…' sources (no quotes) | — |
code | string | Inline script/style/attribute text for hash matching | — |
hash | string | Optional: compute 'sha256-…' and 'sha384-…' sources for this exact text (up to 4 KB) | — |
Example
Request:
GET https://api.oddlathe.com/v1/csp/parse?header=default-src%20%27self%27%3B%20script-src%20%27self%27%20%27unsafe-inline%27&page=https%3A//app.example.com/&check=inline-script&code=alert%281%29
Response (generated by the real code when these docs were last built):
{
"meta_tag": false,
"policy_count": 1,
"grade": "weak",
"policies": [
{
"report_only": false,
"grade": "weak",
"directive_count": 2,
"directives": [
{
"name": "default-src",
"sources": [
"'self'"
],
"meaning": "Fallback for every fetch directive you leave out. Allows: 'self' = the page's own origin (same scheme, host and port).",
"known": true
},
{
"name": "script-src",
"sources": [
"'self'",
"'unsafe-inline'"
],
"meaning": "Where scripts may come from, and whether inline scripts and eval may run. Allows: 'self' = the page's own origin (same scheme, host and port); 'unsafe-inline' = inline code and attributes.",
"known": true
}
],
"findings": [
{
"severity": "error",
"code": "unsafe_inline",
"message": "script-src allows 'unsafe-inline': injected <script> tags and onclick handlers will run. Use nonces or hashes instead."
},
{
"severity": "warn",
…
Code examples
curl -G 'https://api.oddlathe.com/v1/csp/parse' \
--data-urlencode 'header=default-src 'self'; script-src 'self' 'unsafe-inline'' \
--data-urlencode 'page=https://app.example.com/' \
--data-urlencode 'check=inline-script' \
--data-urlencode 'code=alert(1)'const params = new URLSearchParams({
header: "default-src 'self'; script-src 'self' 'unsafe-inline'",
page: "https://app.example.com/",
check: "inline-script",
code: "alert(1)"
});
const res = await fetch(`https://api.oddlathe.com/v1/csp/parse?${params}`);
if (!res.ok) throw new Error((await res.json()).error.message);
const data = await res.json();
console.log(data);import json, urllib.parse, urllib.request
params = urllib.parse.urlencode({"header": "default-src 'self'; script-src 'self' 'unsafe-inline'", "page": "https://app.example.com/", "check": "inline-script", "code": "alert(1)"})
url = "https://api.oddlathe.com/v1/csp/parse?" + params
# Send a User-Agent: the edge network refuses Python's default one.
req = urllib.request.Request(url, headers={"User-Agent": "my-script/1.0"})
with urllib.request.urlopen(req, timeout=10) as r:
data = json.load(r)
print(data)When it's useful
- A CI check that a deploy did not weaken the CSP (grade must stay strong, no unsafe_inline finding)
- Explaining a CSP line in a code review or support answer
- Debugging "Refused to execute inline script" / "Refused to load" console errors with the exact directive named
- Generating the <code>'sha256-…'</code> source for a fixed inline snippet (pass <code>hash</code>)
Good to know
- Send the raw header value, a dump that starts with
Content-Security-Policy:(or…-Report-Only:), or a whole<meta http-equiv="Content-Security-Policy">tag. Several header lines become several policies; a request must pass every enforcing one. gradeisstrong,okayorweak. Each item infindingshas acode, aseverity(error, warn, info) and amessageyou can show as it is.goodslists the positive things the policy already does.- Finding codes include
unsafe_inline,unsafe_eval,broad_script_src,unquoted_keyword,missing_semicolon,unknown_directive,strict_dynamic_empty,meta_ignored,object_src,base_uri,frame_ancestors,form_action,no_script_src,short_nonce,bad_hash,insecure_scheme,public_cdn,report_onlyanddeprecated_directive. - Optional
checkvalues:script,inline-script,attr,eval,style,inline-style,style-attr,img,font,connect,frame,media,worker,object,form,base,ancestor. Passpage(the document URL) and, when needed,url,nonceandcode. The response includesallowed, the decidingdirective, a plain-wordswhy, and the console wording. - It never fetches anything. For a clickable simulator with 17 actions and live hash hashing in the browser, use the CSP header explainer on Tooltern. For framing-only headers see Tooltern's X-Frame-Options checker.
Reads a Content-Security-Policy the way current browsers do: every directive explained in plain words, a grade (strong / okay / weak), findings with severity (unquoted keywords, missing semicolons, 'unsafe-inline', wildcards, missing object-src / base-uri / frame-ancestors, meta-tag traps), and an optional check that applies the fallback chains (script-src-elem → script-src → default-src, worker-src → child-src → …) plus nonce/hash/'strict-dynamic' rules. Pure text analysis: nothing is fetched or stored. Pass hash= to get 'sha256-…' / 'sha384-…' sources for an inline snippet.
Errors
Errors are JSON like {"error": {"code": "…", "message": "…"}}. The ones this endpoint can return:
bad_param(400): a value is the wrong type or out of range (the message says which)rate_limited(429): over 60 requests a minute; wait for Retry-After seconds
Questions
What does a weak grade mean?
At least one high-severity finding: usually <code>'unsafe-inline'</code> without a nonce/hash, a wildcard or <code>https:</code> script source, an unquoted keyword, or no script-src / default-src at all. Fix those before worrying about the notes.
Does default-src cover frame-ancestors?
No. <code>default-src</code> is only the fallback for fetch directives (script-src, img-src, connect-src…). <code>base-uri</code>, <code>form-action</code>, <code>frame-ancestors</code> and <code>sandbox</code> must be set on their own.
Why is 'self' not working?
Without single quotes browsers treat <code>self</code> as a host name. Write <code>'self'</code>. Also note it does not cover other subdomains, or <code>data:</code> / <code>blob:</code> URLs.
Can I put frame-ancestors in a meta tag?
No. <code>frame-ancestors</code>, <code>report-uri</code> and <code>sandbox</code> are ignored in a <code><meta></code> tag. Send them as an HTTP header.
Related endpoints
/v1/frame/checkClickjacking headers: who may embed this page in a frame?/v1/cookie/parseSet-Cookie explainer: will the browser accept this cookie, and why not?/v1/http-statusLook up any HTTP status code: meaning, causes, fixes and retry advice
Prefer a page to an API?
- CSP header explainer with a would-it-be-allowed simulator on Tooltern: same job, nothing leaves your browser.
Published · OpenAPI 3.1 spec · Limits and privacy