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 --jsonThe 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 code | Meaning |
|---|---|
0 | Selected scan completed without observed candidate violations. It does not prove full application coverage. |
1 | Selected scan completed with candidate violations. |
2 | Invalid input, runtime failure or incomplete scan. This takes precedence when violations also exist. |
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.jsonBoth 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
| Symptom | What to check |
|---|---|
| Browser cache ancestor is writable by others | Use 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 error | Run non-root with OS sandbox support and required libraries. Do not disable sandboxing to improve discovery. |
| Existing/meta CSP limits evaluation | Use --bypass-csp for response headers; meta CSP needs an appropriate staging page or separate test. |
| Redirect leaves crawl scope | Start at the canonical final origin. Non-GET or delayed redirects need an explicit final URL/scenario. |
| WebSocket frame attribution is ambiguous | Test the relevant page/flow without mixed-origin ambiguity; do not silently widen permissions. |
| Source-map origins missing from connect-src | Expected: --include-sourcemaps now reports metadata only. Grant permissions only when application behavior justifies them. |
| Non-HTML links in results | They are skipped automatically. --ignore-non-html only suppresses the corresponding notes. |
| No violations but some flows break | The crawler does not exercise every interaction, login state, worker behavior or browser engine. |