CSP reporting for developers

Tighten your CSP without
breaking the site.

Most sites ship a policy that allows everything it was supposed to restrict, because nobody can list every resource their site loads. We watch your real traffic for a week and assemble the tight header from what actually showed up.

A header like thisblocks nothing
default-src 'self';
script-src 'self' 'unsafe-inline' https:;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
no report-uri
Any injected script runs. You never hear about it.
After a week of watchingassembled, not guessed
default-src 'self';
script-src https://example.com 'nonce-{per request}'
  https://www.googletagmanager.com;
style-src 'self' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
frame-src https://js.stripe.com;
base-uri 'self'; form-action 'self';
frame-ancestors 'none';
report-uri https://ingest.csplog.io/…
Every source vouched for by you, and checked against the live site.

One URL to get there. Seven days of watching. No outage on the way.

See the four steps → Launching soon · no SDK · no code change · 14 days free, no card

Four steps. The same four you see inside the app.

How it works

Each of these steps used to be a project of its own. Now each one checks itself against your live site, and nothing waits for you to press a button.

The project page
The csplog project page: reports arrive, every origin gets decided, and the policy goes from Report-Only to enforced.

Step 1 of 4

Deploy the watching header

A deny-all header in Report-Only mode. It blocks nothing. It makes every resource on every page report itself, including the ones your own code loads. Keep your current header next to it.

We read the header back from your site and tick the step when it is right.

Content-Security-Policy-Report-Only:
  default-src 'none';
  base-uri 'none'; form-action 'none'; frame-ancestors 'none';
  report-uri https://ingest.csplog.io/api/csp-report?token=7f3a…
liveexample.com, read back 4 minutes ago

Step 2 of 4

Review the origins

Reports arrive grouped by origin, not by URL. Extensions, antivirus and bots are dropped before they reach you. Known services are named.

You decide each origin once. Unknown ones get an identification from Triage AI. It never decides for you.

Origin · directiveEvents · recentWhatDecision
font-srcfonts.gstatic.com12,480214 pages · 2m agoFontsAllow
script-srcwww.googletagmanager.com8,912198 pages · 1m agoAnalyticsAllow
frame-srcjs.stripe.com1,1023 pages · 9m agoPaymentAllow
img-srcpx.ads.example.net64041 pages · 1h agoAdvertisingIgnore
script-srccdn.unknown-widget.io31812 pages · 4m agoUnknown conf 64—

Browser-extension and antivirus noise is filtered before it reaches this table.

Step 3 of 4

Handle inline code

Inline scripts and styles have no origin to allow. The app finds each one in the reports and offers a path for it: a nonce, a hash, or moving it to a file.

For scripts, 'unsafe-inline' is never suggested and never the default. It goes into the policy only when you accept it explicitly, and the policy is then graded Basic.

<script>window.dataLayer = window.dataLayer || [];</script>
→ hash, one line in the policy:
script-src 'sha256-Qw3fL9v0kR2m…8Kc='

<script nonce="…">trackConsent();</script>
→ nonce, rotated per request, checked on the live page
3 inline scripts, 3 hashes1 nonce, live

Step 4 of 4

Implement the policy

You get an Implementation brief. It carries the committed header byte for byte, the inline mechanism, the site fixes, and the origins you blocked on purpose. Written as requirements, not recipes. We do not know your framework or your deploy. Your coding agent does.

Hand it to Claude Code, Codex, or a colleague. It changes code and stops. You review the diff and ship. We read the live header back and tick the step.

# Implementation brief · example.com

Serve this header on every HTML response. Report-Only first.
  Content-Security-Policy-Report-Only: default-src 'self';
  script-src https://example.com 'nonce-{per request}' https://www.googletagmanager.com;
  …

Rules
  - The header above is the target. Do not add sources.
  - These origins are blocked on purpose: cdn.unknown-widget.io
  - Change code only. Do not deploy, release or restart.
verifiedReport-Only live on 14 pages. Flip to Enforce when a few days pass clean.

From €9 a month.

Pricing

The violation ceilings are abuse bounds, not usage targets. They exist so a runaway script cannot run up a bill. Filtered noise never counts.

Trial€0 14 days Starter · recommended€9 / $10 per month Professional€49 / $55 per month
Projects1110
Violations / month, per account100,0001,000,00010,000,000
Retention30 days30 days90 days
Drift alerts + weekly summaryyesyesyes
Implementation briefyesyesyes
MCP accessnonoyes
Card requirednoyesyes
Get notified Get notified Get notified

One trial per domain. EU customers pay in euros, everyone else in dollars. Prices include VAT and sales tax. Upgrades are immediate and prorated. Downgrades apply at the end of the billing period. Cancel anytime.

Check a site before you sign up.

A check of the headers a public URL serves right now. A grade you can share. No account.

Basic
example.com
example scorecard · your results in ~5 seconds
  • highscript-src allows 'unsafe-inline'
  • mediumNo violation reporting configured
  • lowobject-src is not 'none'
  • lowNo base-uri directive