Document — Culprit Security Posture
Security
Culprit is built around the premise that you should be able to send us your alerts without also sending us your customer data. Here’s how that actually works in practice.
01 / 06 — Tokenization
How tokenization works
Alerts arrive at a Cloudflare Worker that authenticates the sender, encrypts the raw payload with pgp_sym_encrypt, and parks it in a vault table keyed to your tenant. The edge handler returns 200 OK and enqueues the work — no downstream system sees plaintext on the ingest path.
A detector then walks the payload for PII and ePHI (emails, IPs, hostnames, account names, free-text) and swaps each match for a <TOKEN_…> placeholder. The original value is encrypted into a per-tenant token dictionary; the sanitized event is what flows into correlation, LLM prompts, logs, and notifications. Rehydration is a single route — an authenticated reveal endpoint that checks tenant scope on every request before decrypting — and every reveal writes to the audit log.
02 / 06 — Data
Data handling
Four classes of data, four different retention and encryption regimes.
| Data | Location | Encryption | Retention |
|---|---|---|---|
| Raw alert payloads | raw_alerts_vault | pgp_sym_encrypt (AES-256) | Active: indefinite / Closed: 30d |
| Tokenized events | sanitized_events | At-rest (Postgres TDE) | 90 days rolling |
| Token dictionary | token_dictionary | pgp_sym_encrypt (per-tenant key) | Matches parent incident |
| Audit logs | audit_log (pgaudit) | At-rest, append-only | 7 years (SOC2/HIPAA aligned) |
03 / 06 — Isolation
Tenant isolation
Every tenant-scoped table has Row-Level Security enabled, and every policy pins reads and writes to auth.uid()’s tenant membership. The application layer never queries with the Supabase service-role key on a user-facing path — that credential is quarantined to a narrowly scoped admin wrapper that asserts an internal x-admin-verified header set by our middleware. An ESLint rule blocks direct service-role imports in the web app, so a fresh session cannot silently reintroduce a bypass. Tenant crossover is a database-level impossibility, not a convention we trust humans to follow.
04 / 06 — Compliance
Compliance posture
Culprit is architected for SOC2 Type II and HIPAA obligations — encryption-at-rest, tenant isolation by RLS, audit logging via pgaudit, and a documented incident-response process. We are not yet certified/attested. A BAA template is available on request for pilot customers handling PHI; our attestation roadmap (auditor engagement, SOC2 Type I → Type II timeline) is available on request to prospective customers.
05 / 06 — Vendors
Vendors and what they see
Five subprocessors touch the data path. None of them see raw PII.
| Vendor | What they see |
|---|---|
| Supabase | Encrypted payloads and tokenized events at rest. Postgres instance is tenant-isolated via RLS. No vendor employee holds a per-tenant decryption key. |
| Cloudflare | Inbound webhook bodies transit Workers for HMAC verification, then move to Queues as encrypted blobs. Worker logs contain only tokens. |
| Anthropic | Receives tokenized incident context for root-cause analysis. Prompts contain <TOKEN_…> placeholders only. Zero Data Retention is enabled on the API key. |
| OpenAI | Receives tokenized events for embedding. Placeholders only; raw values never leave Postgres. Inputs are not retained for training. |
| Resend | Transactional email (invite links, password resets). Recipient address and the tokenized message body — no incident PII in subject or body. |
06 / 06 — Incident response
Incident response
In the event of a confirmed breach affecting customer data, we notify affected tenants within 72 hours of discovery — consistent with HIPAA breach-notification rules and GDPR-style timelines. Notifications include the scope of the incident, the data classes involved, and the remediation status. Security researchers and customers can reach the security team at: