API reference

Check HTML against a Content-Security-Policy

Parse a Content-Security-Policy, and list what it would block in an HTML document, with the hashes that would allow the inline parts.

GETPOST/api/csp/checkMCP tool csp_check

Parses policy (a Content-Security-Policy header value; commas separate several policies, which must all allow) and any <meta http-equiv="Content-Security-Policy"> in html, then resolves each directive's fallback (CSP Level 3: script-src-elem → script-src → default-src, frame-src → child-src → default-src, …). Given html, it lists every inline script, inline <style>, style attribute, event-handler attribute, javascript: URL and external resource (scripts, stylesheets, images and srcset, fonts and other preloads, media, frames, objects, manifests, form actions, <base>) with allowed, the matching source or the reason it is blocked, the sha256 hash source of inline content, and a fix. hashes_to_add collects, per directive, the hash sources (and 'unsafe-hashes' for attributes) that would allow everything inline that is blocked. Without html, it lints the policy alone. Matching follows CSP Level 3: 'self' (with http→https upgrades), scheme and host sources with *. wildcards, ports and paths, nonces (with the nonceable check), hashes (base64url accepted), 'unsafe-inline' (ignored when a hash or nonce is present), 'unsafe-hashes', 'strict-dynamic' and integrity metadata. A <meta> policy governs only what comes after it. Nothing is fetched: html is the document as you pass it, so content scripts add at run time is not seen. Script elements whose type is not JavaScript (JSON, JSON-LD, templates) are data blocks that CSP never checks; they are counted in summary.ignored_data_blocks.

Browser version (runs locally): CSP Checker.

Try it

Credentials
Remember these credentials

Not saved: you will paste them again next time.

No credentials yet? Get a client ID and secret. They are sent only to this site, in an Authorization: Basic header.

POST /api/csp/check with a JSON body

Parameters

ParameterTypeDefaultDescription
policystring (≤ 20000 chars)—The Content-Security-Policy header value. Optional when html contains a <meta http-equiv="Content-Security-Policy">; when both are given, both are enforced.
htmlstring (≤ 500000 chars)—The HTML document to check, as the server sends it (for example a built page). Optional: without it, only the policy is analysed.
urlstring (≤ 2000 chars)—The page's own URL, used for 'self' and to resolve relative URLs. Default: relative URLs count as same-origin and absolute ones as cross-origin.

Also accepted on every endpoint: format (json | text). Parameters may come from the query string, a JSON object body, an urlencoded or multipart form, or (for input) a text/plain body.

Global parameters
ParameterTypeDefaultDescription
formatjson | textjsonjson returns the documented object. text returns only the primary value as text/plain (see "Text output").

Response 200 application/json

FieldTypeDescription
originstring | nullThe page origin used for 'self', or null when url was not given.
policiesarray of objectThe policies enforced: the header ones first, then each <meta> policy.
policies[].source"header" | "meta"Where the policy came from.
policies[].lineinteger | nullLine of the <meta> element; null for the header.
policies[].textstringThe policy as written.
policies[].directivesarray of objectDirectives in order; a repeated directive is ignored (see warnings).
policies[].directives[].namestringDirective name, lower-cased.
policies[].directives[].sourcesarray of stringIts source list, as written.
policies[].effectivemap of string | nullFor each effective directive (script-src-elem, img-src, …), the directive that governs it.
policies[].warningsarray of stringUnknown or repeated directives, sources missing quotes, 'unsafe-inline' or wildcards in script lists, missing object-src or base-uri, directives a <meta> policy cannot set.
findingsarray of objectIn document order.
findings[].kind"inline-script" | "inline-style" | "style-attribute" | "event-handler" | "javascript-url" | "external-script" | "external-style" | "image" | "font" | "media" | "frame" | "object" | "manifest" | "connect" | "form-action" | "base-uri"What was found.
findings[].lineintegerLine in the HTML (1-based).
findings[].snippetstringThe markup, shortened to 120 characters.
findings[].directivestringThe effective directive that governs it, e.g. script-src-elem.
findings[].allowedbooleanTrue when every policy allows it.
findings[].bystring | nullThe source that allowed it (per policy when there are several); (no directive) when nothing restricts it.
findings[].reasonstring | nullWhy it is blocked, naming the directive that applied.
findings[].urlstring | nullThe resolved URL of an external resource.
findings[].hashstring | nullThe 'sha256-…' hash source of the inline content (script or style text, attribute value), computed as browsers do.
findings[].fixstring | nullHow to allow it, when it is blocked.
hashes_to_addarray of objectHash sources that would allow every blocked inline script, style, style attribute and event handler.
hashes_to_add[].directivestringThe directive to add them to (the one that blocked them).
hashes_to_add[].sourcesarray of stringHash sources, in document order.
hashes_to_add[].notestringWhat adding them changes.
summaryobject
summary.totalintegerFindings.
summary.allowedintegerFindings the policies allow.
summary.blockedintegerFindings a policy blocks.
summary.ignored_data_blocksintegerScript elements with a non-JavaScript type (JSON, JSON-LD, …), which CSP never checks.
warningsarray of stringUnclosed raw-text elements, invalid URLs, content before a <meta> policy.

Text output (format=text, text/plain): allowed or blocked: <n>, then one line per blocked finding: <line> <kind> <directive>: <reason>.

Errors

Errors are JSON: {"error": "<message>", "code": "<code>", "param": "<name>"?}.

StatusCodeWhen
400invalid_parameterNeither policy nor a <meta> policy in html was given, url is not an absolute http(s) URL, or a parameter exceeds its length limit.
400missing_parameterA required parameter is absent.
400unknown_parameterA parameter name is not in the table above (typos are rejected, not ignored).
413payload_too_largeThe request body exceeds 1 MiB.
405method_not_allowedA method other than GET, POST or OPTIONS.

Behaviour

  • Idempotent: the same arguments always return the same result.
  • Requires a client ID and secret (authentication). Each call is recorded as one metadata row against your client ID; inputs and outputs are never stored or logged. Responses carry cache-control: no-store.
  • CORS: any origin may call this endpoint from a browser, using your credentials.

Examples

Responses below were produced by running each request through this code at build time. Long strings are shortened.

An inline script, a style attribute and an external script

curl -X POST 'https://util.caruana.me/api/csp/check' \
  -H 'content-type: application/json' \
  -d '{"policy":"default-src '\''none'\''; script-src '\''self'\''; style-src '\''self'\''","html":"<script>init()</script><p style=\"color:red\">x</p><script src=\"/app.js\"></script>"}'

Response 200

{
  "origin": null,
  "policies": [
    {
      "source": "header",
      "line": null,
      "text": "default-src 'none'; script-src 'self'; style-src 'self'",
      "directives": [
        {
          "name": "default-src",
          "sources": [
            "'none'"
          ]
        },
        {
          "name": "script-src",
          "sources": [
            "'self'"
          ]
        },
        {
          "name": "style-src",
          "sources": [
            "'self'"
          ]
        }
      ],
      "effective": {
        "script-src-elem": "script-src",
        "script-src-attr": "script-src",
        "style-src-elem": "style-src",
        "style-src-attr": "style-src",
        "worker-src": "script-src",
        "frame-src": "default-src",
        "connect-src": "default-src",
        "font-src": "default-src",
        "img-src": "default-src",
        "manifest-src": "default-src",
        "media-src": "default-src",
        "object-src": "default-src",
        "base-uri": null,
        "form-action": null,
        "frame-ancestors": null
      },
      "warnings": [
        "No base-uri: an injected <base> can redirect relative script URLs. base-uri 'none' or 'self' closes that."
      ]
    }
  ],
  "findings": [
    {
      "kind": "inline-script",
      "line": 1,
      "snippet": "<script>init()",
      "directive": "script-src-elem",
      "url": null,
      "hash": "'sha256-w4ujnOpjBoH2vcasx+reJRUwYivG8Q3afx/XevGJod8='",
      "fix": "Move it to an external file, or add 'sha256-w4ujnOpjBoH2vcasx+reJRUwYivG8Q3afx/XevGJod8=' to script-src.",
      "allowed": false,
      "by": null,
      "reason": "script-src: no 'unsafe-inline', no matching nonce or hash"
    },
    {
      "kind": "style-attribute",
      "line": 1,
      "snippet": "<p style=\"color:red\">",
      "directive": "style-src-attr",
      "url": null,
      "hash": "'sha256-8f935d27GvUutRyY9yWScUMiFUk4WTdZURISiYfPOeQ='",
      "fix": "Move the style attribute into an external stylesheet (a class), or add 'unsafe-hashes' 'sha256-8f935d27GvUutRyY9yWScUMiFUk4WTdZURISiYfPOeQ=' to style-src.",
      "allowed": false,
      "by": null,
      "reason": "style-src: no 'unsafe-inline', no matching hash"
    },
    {
      "kind": "external-script",
      "line": 1,
      "snippet": "<script src=\"/app.js\">",
      "directive": "script-src-elem",
      "url": "/app.js",
      "hash": null,
      "fix": null,
      "allowed": true,
      "by": "'self'",
      "reason": null
    }
  ],
  "hashes_to_add": [
    {
      "directive": "script-src",
      "sources": [
        "'sha256-w4ujnOpjBoH2vcasx+reJRUwYivG8Q3afx/XevGJod8='"
      ],
      "note": "Adding a hash or nonce makes browsers ignore 'unsafe-inline' in the same directive."
    },
    {
      "directive": "style-src",
      "sources": [
        "'unsafe-hashes'",
        "'sha256-8f935d27GvUutRyY9yWScUMiFUk4WTdZURISiYfPOeQ='"
      ],
      "note": "Includes 'unsafe-hashes', which lets these hashes allow event handlers and style attributes. Adding a hash or nonce make… (56 more characters)"
    }
  ],
  "summary": {
    "total": 3,
    "allowed": 1,
    "blocked": 2,
    "ignored_data_blocks": 0
  },
  "warnings": []
}
Same call as an MCP tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "csp_check",
    "arguments": {
      "policy": "default-src 'none'; script-src 'self'; style-src 'self'",
      "html": "<script>init()</script><p style=\"color:red\">x</p><script src=\"/app.js\"></script>"
    }
  }
}

Nonces, a CDN and a <meta> policy

curl -X POST 'https://util.caruana.me/api/csp/check' \
  -H 'content-type: application/json' \
  -d '{"html":"<!doctype html>\n<head>\n<meta http-equiv=\"Content-Security-Policy\" content=\"script-src '\''nonce-r4nd0m'\'' https://cdn.example.com; img-src '\''self'\'' data:\">\n<script nonce=\"r4nd0m\">start()</script>\n<script src=\"https://cdn.example.com/lib.js\"></script>\n<script src=\"https://evil.example.net/x.js\"></script>\n</head>\n<body><img src=\"/logo.png\"><img src=\"https://img.example.org/a.png\"><button onclick=\"go()\">Go</button></body>","url":"https://www.example.com/"}'

Response 200

{
  "origin": "https://www.example.com",
  "policies": [
    {
      "source": "meta",
      "line": 3,
      "text": "script-src 'nonce-r4nd0m' https://cdn.example.com; img-src 'self' data:",
      "directives": [
        {
          "name": "script-src",
          "sources": [
            "'nonce-r4nd0m'",
            "https://cdn.example.com"
          ]
        },
        {
          "name": "img-src",
          "sources": [
            "'self'",
            "data:"
          ]
        }
      ],
      "effective": {
        "script-src-elem": "script-src",
        "script-src-attr": "script-src",
        "style-src-elem": null,
        "style-src-attr": null,
        "worker-src": "script-src",
        "frame-src": null,
        "connect-src": null,
        "font-src": null,
        "img-src": "img-src",
        "manifest-src": null,
        "media-src": null,
        "object-src": null,
        "base-uri": null,
        "form-action": null,
        "frame-ancestors": null
      },
      "warnings": [
        "No default-src, so these are unrestricted: style-src-elem, style-src-attr, frame-src, connect-src, font-src, manifest-src, media-src, object-src.",
        "object-src is not 'none': plugins (<object>, <embed>) can load. object-src 'none' is the usual hardening.",
        "No base-uri: an injected <base> can redirect relative script URLs. base-uri 'none' or 'self' closes that."
      ]
    }
  ],
  "findings": [
    {
      "kind": "inline-script",
      "line": 4,
      "snippet": "<script nonce=\"r4nd0m\">start()",
      "directive": "script-src-elem",
      "url": null,
      "hash": "'sha256-DIm7WJS6ZKDYe5qFLPy+h4JFI9Bol5QmYC57mt3Fb00='",
      "fix": null,
      "allowed": true,
      "by": "'nonce-r4nd0m'",
      "reason": null
    },
    {
      "kind": "external-script",
      "line": 5,
      "snippet": "<script src=\"https://cdn.example.com/lib.js\">",
      "directive": "script-src-elem",
      "url": "https://cdn.example.com/lib.js",
      "hash": null,
      "fix": null,
      "allowed": true,
      "by": "https://cdn.example.com",
      "reason": null
    },
    {
      "kind": "external-script",
      "line": 6,
      "snippet": "<script src=\"https://evil.example.net/x.js\">",
      "directive": "script-src-elem",
      "url": "https://evil.example.net/x.js",
      "hash": null,
      "fix": "Add https://evil.example.net (or the exact URL) to script-src.",
      "allowed": false,
      "by": null,
      "reason": "script-src has no source matching https://evil.example.net"
    },
    {
      "kind": "image",
      "line": 8,
      "snippet": "<img src=\"/logo.png\">",
      "directive": "img-src",
      "url": "https://www.example.com/logo.png",
      "hash": null,
      "fix": null,
      "allowed": true,
      "by": "'self'",
      "reason": null
    },
    {
      "kind": "image",
      "line": 8,
      "snippet": "<img src=\"https://img.example.org/a.png\">",
      "directive": "img-src",
      "url": "https://img.example.org/a.png",
      "hash": null,
      "fix": "Add https://img.example.org (or the exact URL) to img-src.",
      "allowed": false,
      "by": null,
      "reason": "img-src has no source matching https://img.example.org"
    },
    {
      "kind": "event-handler",
      "line": 8,
      "snippet": "<button onclick=\"go()\">",
      "directive": "script-src-attr",
      "url": null,
      "hash": "'sha256-5KYv+PUboo5h+0+YAtGRPbwv5d/QxzHslP4YGnUaxRw='",
      "fix": "Move the event handler into an external script (addEventListener), or add 'unsafe-hashes' 'sha256-5KYv+PUboo5h+0+YAtGRPbwv5d/QxzHslP4YGnUaxRw=' to script-src.",
      "allowed": false,
      "by": null,
      "reason": "script-src: no 'unsafe-inline', no matching hash"
    }
  ],
  "hashes_to_add": [
    {
      "directive": "script-src",
      "sources": [
        "'unsafe-hashes'",
        "'sha256-5KYv+PUboo5h+0+YAtGRPbwv5d/QxzHslP4YGnUaxRw='"
      ],
      "note": "Includes 'unsafe-hashes', which lets these hashes allow event handlers and style attributes. Adding a hash or nonce make… (56 more characters)"
    }
  ],
  "summary": {
    "total": 6,
    "allowed": 3,
    "blocked": 3,
    "ignored_data_blocks": 0
  },
  "warnings": []
}
Same call as an MCP tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "csp_check",
    "arguments": {
      "html": "<!doctype html>\n<head>\n<meta http-equiv=\"Content-Security-Policy\" content=\"script-src 'nonce-r4nd0m' https://cdn.example.com; img-src 'self' data:\">\n<script nonce=\"r4nd0m\">start()</script>\n<script src=\"https://cdn.example.com/lib.js\"></script>\n<script src=\"https://evil.example.net/x.js\"></script>\n</head>\n<body><img src=\"/logo.png\"><img src=\"https://img.example.org/a.png\"><button onclick=\"go()\">Go</button></body>",
      "url": "https://www.example.com/"
    }
  }
}

Lint a policy on its own

curl 'https://util.caruana.me/api/csp/check?policy=default-src%20'\''self'\''%3B%20script-src%20'\''self'\''%20'\''unsafe-inline'\''%20'\''unsafe-eval'\''%20https%3A'

Response 200

{
  "origin": null,
  "policies": [
    {
      "source": "header",
      "line": null,
      "text": "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:",
      "directives": [
        {
          "name": "default-src",
          "sources": [
            "'self'"
          ]
        },
        {
          "name": "script-src",
          "sources": [
            "'self'",
            "'unsafe-inline'",
            "'unsafe-eval'",
            "https:"
          ]
        }
      ],
      "effective": {
        "script-src-elem": "script-src",
        "script-src-attr": "script-src",
        "style-src-elem": "default-src",
        "style-src-attr": "default-src",
        "worker-src": "script-src",
        "frame-src": "default-src",
        "connect-src": "default-src",
        "font-src": "default-src",
        "img-src": "default-src",
        "manifest-src": "default-src",
        "media-src": "default-src",
        "object-src": "default-src",
        "base-uri": null,
        "form-action": null,
        "frame-ancestors": null
      },
      "warnings": [
        "script-src: 'unsafe-inline' allows every inline script and event handler, which defeats most XSS protection.",
        "script-src: 'unsafe-eval' allows eval() and new Function().",
        "script-src: https: lets scripts load from anywhere that scheme reaches.",
        "object-src is not 'none': plugins (<object>, <embed>) can load. object-src 'none' is the usual hardening.",
        "No base-uri: an injected <base> can redirect relative script URLs. base-uri 'none' or 'self' closes that."
      ]
    }
  ],
  "findings": [],
  "hashes_to_add": [],
  "summary": {
    "total": 0,
    "allowed": 0,
    "blocked": 0,
    "ignored_data_blocks": 0
  },
  "warnings": []
}
Same call as an MCP tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "csp_check",
    "arguments": {
      "policy": "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:"
    }
  }
}