Content-Security-Policy is the browser feature that stops injected scripts, rogue frames and exfiltration by refusing to load anything the page didn't declare. It comes with a built-in feedback channel: the browser will POST a report every time it blocks something. Almost nobody listens to that channel, because the hosted services that do are metered per report and the raw stream is a firehose of noise. Cribado-CSP is the listener you run yourself, shipped as one small self-hosted binary.
The premise in one line: a CSP report stream is a dedup problem, not a logging problem. A policy misfiring a thousand times a minute is one fact with a big counter, not a thousand rows. Store the fact once, count it, and watch the counters for the handful of changes that mean something.
How it works
One endpoint, both formats, and plain SQL where you'd expect magic.
One endpoint, both report formats
Browsers speak two dialects: the legacy report-uri POST and the Reporting-API report-to batch. Cribado-CSP accepts both at the same URL, content-negotiated, so old and new browsers land in the same table.
Dedup-and-count, not a firehose
Each distinct violation โ same site, directive, blocked host, disposition โ is one row with a counter. Volume becomes a number on a row instead of a runaway table, and the interesting signal is the counter's slope.
Deterministic insights
The warnings โ a never-before-seen blocked host, a directive suddenly firing far above its baseline, a real enforce-mode breakage โ are plain SQL thresholds you can read and audit. An optional ๐ค summary layer is planned on top, opt-in; the tool works fully without it.
Pointing a page at it
Two response headers, and the browser does the rest.
Each site gets an opaque ingest key. Add the reporting directive to the page's policy โ legacy form, Reporting-API form, or both:
Content-Security-Policy-Report-Only:
default-src 'self'; report-uri https://<host>/r/<key>
# or, via the Reporting API:
Reporting-Endpoints: cribado-csp="https://<host>/r/<key>"
Content-Security-Policy-Report-Only:
default-src 'self'; report-to cribado-csp
Start in report-only mode to see what a policy would break, tighten it against real data, then enforce. Sites and keys are managed from the dashboard โ create sites, mint and disable keys, copy ingest URLs โ or from equivalent ops subcommands for the first key on a fresh box.
The trade-off, stated plainly
Cribado-CSP's operators can read every violation it stores. On purpose.
Cribado is built on CARLOS, whose family rule is server-blindness: the server holds only ciphertext it cannot open. Cribado deviates, deliberately, because the product is analytics over the reports โ you cannot cheaply compute "top blocked hosts" over data you cannot read. A CSP report is operational telemetry about your own apps, closer to a server log than to a private message. The family rule for deviations applies instead: enumerated, justified, published, never hidden. No end-to-end encryption is claimed, here or anywhere.
- Storage is still minimised. URL query strings and fragments are stripped before storage โ a
document-urican carry a user token โ and inline-script samples are never shown in list or aggregate views. Browsers don't put cookies or auth headers in reports, so Cribado-CSP never sees them. - The ingest endpoint is public and unauthenticated, by necessity: browsers can't sign a report POST. It is hostile by construction โ rate-limited, size-capped, scoped by an opaque per-site key, with unknown keys rejected cheaply.
- The dashboard is gated. Sign-in is Keymail OAuth SSO, restricted to an operator allowlist; only operators can read violation data. A deployed build fails closed if SSO isn't configured, so an open dashboard cannot ship by accident.
Status
Fewer claims, all of them checkable.
v1, in progress. The ingest endpoint and the analytics dashboard run today; deployment to production is the current work. Cribado-CSP exists to watch its makers' own apps first โ it becomes interesting to anyone else only after it has earned its keep there. The source is not yet published; the licence is being decided before first publication, and open source is the family default โ not optional, only deferred. When that changes, this page will link to it.