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 family name is cribado — Spanish for sieving, the work of a criba — because that is the whole idea: a sieve for the report stream, keeping the grain and shedding the noise.
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's operators can read every report 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, socribado-cspnever 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.
One idea, several streams
Every reporting protocol deserves the same sieve.
cribado-csp
The CSP-violation sieve — the app this page describes. v1, in progress: the ingest endpoint and the analytics dashboard run today; deployment is the current work.
cribado-dmarc
The same treatment for DMARC aggregate reports — the XML your mail domain receives about who is sending as you. Not yet built. It is named here so the shape of the family is honest, not to imply it exists.
Status
Fewer claims, all of them checkable.
v1, in progress. cribado-csp's ingest endpoint and analytics dashboard run today; deployment to production is the current work. Cribado 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.
Part of a family
One binary, one SQLite database, no build step.
Cribado apps are CARLOS applications, built with the Rastrillo web framework: each a single static Go binary with the database compiled in, deployed as one instance, small enough for one person to read. This site follows the same rule — one static page, no build step, no dependencies, no JavaScript.