Security & privacy
Keys
- API keys (
cpl_…) authorize everything in a project. They are 200-bit secrets; only their SHA-256 is stored. Keep them server-side. - Ingest keys (
cs_…, in the snippet) are public and write-only: they can only send web analytics events to their project. They cannot read data or claim the project. - Claim secrets (
csk_…) are 160-bit random secrets generated in your browser on capless.sh (or byGET https://analytics.capless.sh/new). The ingest key is derived from the secret (SHA-256), and the project ID from the key, so Capless never needs to store the secret. capless.sh keeps it in your browser'slocalStorage, so the page shows the same snippet on your next visit. While the project is unclaimed, the secret gives read-only access to it; claiming the project for an account requires it. Once claimed, it no longer grants access (401 project_claimed). - Allowed origins (optional, off by default) make browsers on sites that copied your snippet get
403 origin_not_allowed. This is a deterrent, not a security boundary: anyone can send events with their own HTTP client. The protection for your bill is a spend cap, which pauses collection when the month's estimated bill reaches it. - New projects (the first event of an unknown key) are rate limited per IP address, and unclaimed projects have hard caps and are deleted after 14 days.
Privacy by default
- No cookies. The SDK keeps an anonymous ID in
localStorage. - Raw IP addresses are never stored; events carry
ipHash, a keyed hash. - Form submissions record the form, never field values.
- Session replay masks every input value and never records password fields, script contents or inline event handlers. Mask text with
.capless-maskand remove whole subtrees with.capless-block(details). capless("optout")stops collection in a browser.- Erasures delete data by predicate from storage and every materialization, with auditable status.
- Data is kept until you delete it.
Isolation and bounds
- CEL has no I/O, clock, randomness or user functions. Expressions and inputs are size and depth bounded;
matchesrejects patterns that can backtrack catastrophically; every request runs under the Workers CPU limit. - ClickHouse: one database and user per materialization, a settings profile with hard maxima (time, memory, result size), no external table functions (
url,file,s3,remote, …), partial revokes on managed tables. - Per-materialization ClickHouse credentials are stored in the project's Durable Object (encrypted at rest by Cloudflare).
Infrastructure
Capless runs on Cloudflare: Workers for the API and collector, Durable Objects (one per stream, project and materialization source) for ordering and metadata, R2 for record storage. Materializations run on ClickHouse Cloud. There are no servers or VMs to patch.
- Writes are acknowledged only after they are durable in R2 and committed to the stream's history.
- Everything is served over HTTPS.
Supply chain
The codebase installs no dependency lifecycle scripts, pins exact versions, refuses releases younger than 7 days and refuses provenance downgrades. The browser SDK has no dependencies at all.
Reporting a vulnerability
Email hello@capless.sh. Please do not open public issues for security reports.