Security
ClickStream is built on the principle that operators should be able to verify our security posture without taking our word for it. Every sensitive pathway — raw-PII reveal, API key rotation, admin actions — is logged, rate-limited, and scoped to a specific operator identity.
This page covers the production security posture. For day-to-day privacy controls (consent, banners, data residency), see Privacy & compliance.
Encryption at rest
Every field containing raw PII is AES-256-GCM encrypted before it touches persistent storage, under a unique symmetric key per customer site. Keys are managed via Cloudflare's Secrets Store binding; the collector never sees plaintext keys — it requests encrypt / decrypt operations via a narrow API.
Encrypted surfaces:
- Email addresses sent to the collector for server-side hashing (the SDK hashes first; the collector encrypts the pre-hash ciphertext for audit-recoverability only).
- Phone numbers same pattern.
- IP addresses — raw IP is encrypted alongside a SHA-256 hash (for household grouping on the hashed value, reveal only via the gate).
- Form-submission values — universal form-fill capture encrypts name→value pairs + file metadata + text excerpts.
The encrypted blobs live in Dashboard D1 (visitor_encrypted_fields, form_submissions). The SHA-256 hashes live in Analytics Engine (blob11 / blob12 / blob20 per the event schema).
Per-tenant HMAC isolation
Hashed identity signals in Analytics Engine are HMAC'd with a per-tenant key before storage. That means:
- Two tenants can't correlate the same visitor across their datasets even if they know each other's raw hashes.
- A compromised HMAC key affects exactly one tenant.
- HMAC rotation (planned quarterly) breaks no existing analytics because the schema carries both
hmacHem(current) andhemMd5(public) in separate fields.
Raw-value reveal — the /decrypt gate
Operators with decrypt:read permission can reveal raw encrypted values on a per-visitor basis via the dashboard. Every reveal passes four independent checks:
- Authentication — valid dashboard session.
- Permission — operator's role includes
decrypt:read. - Password re-authentication — operator has re-entered their password within the last 5 minutes (
/api/auth/decrypt-unlock). Session auth alone isn't enough; re-auth is required for every sensitive action. - Rate limit — 10 reveals per operator per 5-minute window.
- Audit — an
audit_logrow is written with operator id, IP, visitor id, field type, timestamp, and the required free-text reason ("suspected fraud", "user support ticket #1234", …).
The audit log is append-only. Admins see the full reveal history on the Security → Reveal Audit tab, filterable by operator and date. Audit rows are retained 7 years (SOC2 window) and exportable as CSV.
API key lifecycle
- Storage — keys live in Cloudflare KV under
apikey:{key}. The collector hot-cachesClientConfigwith a 2-minute LRU for performance (~99.5% hit rate in production). - Permission scopes —
events:write,events:read,live:read,identity:resolve,signals:read— see API keys + auth. - Rotation — mint a new key in the dashboard → deploy to your site config → wait 7 days → delete the old key. The 2-minute hot cache + 30-second negative cache means rotation takes effect globally within ~2 minutes after the KV write.
- Revocation — instant via the dashboard. The key is deleted from KV and cached negative-lookup entries flush within 30 seconds.
Keys are opaque bearer credentials. A compromised key grants whatever scopes it carries until revoked — same trust model as Stripe restricted keys. Treat them accordingly: CI secret managers, per-environment keys, cs_test_ prefix for staging.
Admin surface — X-Admin-Key
A handful of administrative endpoints (custom hostname provisioning, KV bootstrap, force-refresh cache) require the ADMIN_API_KEY secret via X-Admin-Key header instead of the standard API key. The admin secret is set via wrangler secret put ADMIN_API_KEY and rotated quarterly. It's not issued to customers — the dashboard calls these endpoints on your behalf when you, say, provision a new tracking domain.
Rate limiting
The collector enforces per-API-key rate limits with a sliding-window counter backed by a Cloudflare Durable Object (SOC2-friendly "authoritative source"). Default caps per tier:
| Tier | Events / sec |
|---|---|
| Free | 100 |
| Builder | 1,000 |
| Scale | 5,000 |
| Network | 25,000 |
| Custom | 100,000 |
Burst allowance is 2× the sustained rate for short spikes. See Rate limits for the response headers + overage behavior.
TLS / transport
- TLS 1.3 required on all customer endpoints. TLS 1.2 accepted for legacy browsers but not recommended. TLS 1.1 and below rejected.
- HSTS with
max-age=31536000; includeSubDomains; preloadon every response. - Cipher suites — the Cloudflare edge negotiates; we default to modern suites only.
cf.botManagement.ja3Hash+ja4are captured server-side and used in stealth bot detection.
Content Security Policy
The collector returns CSP headers on every response (content-security-policy: default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'). The SDK loader + bundle are served from your first-party domain, so the customer's own CSP can allow-list t.yourdomain.com without needing a third-party domain exception. Per-site CSP overrides are configurable on the Site → Security tab.
Audit log — what's captured
Every action of consequence on the dashboard writes to audit_log:
- Sign-in / sign-out (success + failure).
- Password change / reset.
- API key create / rotate / revoke.
- Site create / delete / rename.
- Tracking domain add / verify / remove.
- Operator invite / remove / role change.
/decryptreveals (plus the reason text).- Data export / delete.
- Consent-profile changes.
Retention: 7 years, append-only. Exportable as CSV from Security → Audit Log.
Incident response
- Security issue reporting —
security@clickstream.com. We triage within 4 hours, 24/7. Disclosure coordination follows the 90-day industry standard unless actively exploited in the wild. - Bug bounty — not formally launched pre-GA; we engage with coordinated disclosures case-by-case and will backdate rewards on the eventual bounty program.
- Status page —
status.clickstream.com(coming live alongside the public launch).
Roadmap
- SOC2 Type II — target by end of Q3 2026. The audit-log, key-rotation, encryption, and access-control pieces are in place; the residual work is operational (access reviews, change-management logs, vendor management).
- AWS Nitro Enclaves for UID2 — private-operator mode on the enterprise roadmap. Currently UID2 routes through The Trade Desk's public operator; enterprise customers wanting ClickStream as a private operator should contact sales@clickstream.com.
- Formal penetration test — scheduled for the launch quarter; results published under NDA.
Data residency
Covered on the Privacy & compliance page. Cloudflare's data-at-rest regions are enforced via tenant-specific wrangler.toml on Custom tier; all other tiers inherit Cloudflare's default global distribution.
See also
- Privacy & compliance — consent + GDPR / CCPA / HIPAA modes
- API keys + auth — permission scopes + rotation runbook
- Identity resolution — what's hashed where
security@clickstream.com— reach out for DPA, pen-test letter, or incident reporting