AnvilJSON utilities

Anvil › Docs › /v1/csp/parse

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?".

In one line: GET /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.
GET

/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

NameTypeWhat it doesDefault
headerstringA Content-Security-Policy value, a header dump (with or without the name), or a <meta> tag. Up to 8 KB.—
pagestringDocument URL used by the optional check simulator (scheme+host+port)—
checkstringOptional action to test: script, inline-script, attr, eval, style, inline-style, style-attr, img, font, connect, frame, media, worker, object, form, base, ancestor—
urlstringResource / form / ancestor URL for the check (not needed for eval or inline kinds)—
noncestringNonce value to try against 'nonce-…' sources (no quotes)—
codestringInline script/style/attribute text for hash matching—
hashstringOptional: 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
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)'
JavaScript (browser or Node 18+)
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);
Python (standard library only)
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.
  • grade is strong, okay or weak. Each item in findings has a code, a severity (error, warn, info) and a message you can show as it is. goods lists 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_only and deprecated_directive.
  • Optional check values: script, inline-script, attr, eval, style, inline-style, style-attr, img, font, connect, frame, media, worker, object, form, base, ancestor. Pass page (the document URL) and, when needed, url, nonce and code. The response includes allowed, the deciding directive, a plain-words why, 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>&lt;meta&gt;</code> tag. Send them as an HTTP header.

Related endpoints

Prefer a page to an API?

Published · OpenAPI 3.1 spec · Limits and privacy