Test a CSP before enforcing it

Inspect violations and coverage before relying on a candidate policy.

Supply a policy

cspresso https://example.com/ --bypass-csp \
  --evaluate "default-src 'self'; object-src 'none'" --json

# Or read a UTF-8 policy file:
cspresso https://example.com/ --bypass-csp \
  --evaluate-file candidate-csp.txt --json

The candidate is injected as Content-Security-Policy-Report-Only on same-origin HTML. Structured violations are attributed to that candidate and relevant documents. Foreign frame policies are not rewritten, and page-authored console strings are not treated as trusted violation reports.

Interpret the result

Exit codeMeaning
0Selected scan completed without observed candidate violations. It does not prove full application coverage.
1Selected scan completed with candidate violations.
2Invalid input, runtime failure or incomplete scan. This takes precedence when violations also exist.
Check completeness as well as violations
Inspect complete, errors and each entry in pages. Failed or unconfirmed injection, remaining enforcing policies, HTTP/navigation failures and exceeded safety budgets must not become a clean pass. An empty violation list alone is insufficient.

Report-Only does not reproduce every effect of enforcement. A clean run means no violations were observed during completed selected coverage, not that every application flow works. Test forms, embedding, login states and real interactions separately before deployment.

Existing CSP and the Chromium sandbox

An existing enforcing policy can block loads before the candidate sees them. Use --bypass-csp to strip existing same-origin HTML response headers. This does not remove meta CSP; any remaining meta policy makes evaluation incomplete. Existing report-only headers are replaced during evaluation.

Chromium’s process sandbox remains enabled. It contains browser processes and does not need to be disabled for CSP discovery. If bypassing a site’s policy enables previously blocked injected code, that still changes risk: see Security.

CI example

After installing CSPresso and browser dependencies on a non-root, sandbox-capable runner, use a step like this in Forgejo or GitHub Actions. Keep candidate-csp.txt in the checkout and replace the example URL.

- name: Evaluate candidate CSP
  run: |
    cspresso https://example.com/ \
      --bypass-csp \
      --evaluate-file candidate-csp.txt \
      --scan-timeout 120 \
      --json > csp-report.json

Both exit 1 and exit 2 fail the step. Retain the JSON as a restricted CI artifact using your runner’s artifact workflow, including on failure. Reuse an owned browser cache through --browsers-path. Do not automatically deploy a draft generated by the same scan.

Troubleshooting

SymptomWhat to check
Browser cache ancestor is writable by othersUse an owned private --browsers-path outside the permissive directory. The source test script now creates a temporary private cache by default; do not weaken the permission checks.
Exit 2 with a sandbox launch errorRun non-root with OS sandbox support and required libraries. Do not disable sandboxing to improve discovery.
Existing/meta CSP limits evaluationUse --bypass-csp for response headers; meta CSP needs an appropriate staging page or separate test.
Redirect leaves crawl scopeStart at the canonical final origin. Non-GET or delayed redirects need an explicit final URL/scenario.
WebSocket frame attribution is ambiguousTest the relevant page/flow without mixed-origin ambiguity; do not silently widen permissions.
Source-map origins missing from connect-srcExpected: --include-sourcemaps now reports metadata only. Grant permissions only when application behavior justifies them.
Non-HTML links in resultsThey are skipped automatically. --ignore-non-html only suppresses the corresponding notes.
No violations but some flows breakThe crawler does not exercise every interaction, login state, worker behavior or browser engine.