Cerberus
Security
Last updated 19 August 2026
The questions a security review asks, answered once, here. Every answer describes the system as it is built today. Where something does not exist, it says that it does not exist, rather than describing what it would look like.
1What this document is
A factual description, not a contract. It names no party and asks you to agree to nothing. See the privacy notice for what Cerberus receives and what it cannot do; this page is about how the thing holding it is built.
2What Cerberus holds
Cerberus receives twelve fields of usage metadata per request, and nothing else. The ingest endpoint rejects any field it does not recognise, by name. There is no field for an API key, a prompt, a response, an IP address, a user identifier, or an email address.
Keys and IP addresses are fingerprinted before anything is transmitted, using HMAC-SHA256 under a salt you generate and keep. The salt is never sent to us. This is the load-bearing property of the whole design: an attacker who took the entire database would hold fingerprints they cannot reverse without a secret that was never there.
The same applies to identifiers in the URL path. Our client collapses them before sending — numbers and UUIDs become {id}, and high-entropy segments become {token}. That second one is not about tidiness: a signed token sitting in a path is a credential, and collapsing it on your machine means it never reaches us at all. If you post to the API directly rather than through our client, we collapse the same shapes on receipt, so a token cannot be stored either way.
3How secrets are stored
Your ingest token is stored as a SHA-256 digest. The token itself is shown once, when it is issued, and is not recoverable afterwards. It is a 256-bit random value rather than a password, which is why the digest is unsalted and uniterated: there is no smaller space to search.
Your Slack webhook URL is stored in plaintext, and cannot be anything else. It has to be replayed verbatim to post a message, so there is no version of this that hashes it. Anyone with database access has it. What it grants is the ability to post into the channel you pointed it at, and nothing more — it does not read messages and it is not a Slack login. It is revocable from your side at any time, in Slack, without involving us.
Our own secrets — the database connection string, the token that signs acknowledgement links, the cron authentication token, the operator dashboard password and its separate session-signing key, and our own operations webhook URL — are environment variables held by Vercel. None are in the source repository. They are deliberately separate values rather than one reused secret, so that rotating any one of them does not invalidate the others and a leak of one does not compromise the rest.
That list is complete, and the last item was missing from it until an audit on 14 August 2026. Our operations webhook is a bearer credential in exactly the way a customer’s is — anyone holding it can post into our Slack — and a security page that inventories secrets is worth nothing if the inventory is partial. It is named here for the same reason section 3 discloses that customer webhooks are stored in plaintext.
Action links in alerts do not expire. The acknowledge link in a Slack alert and the unsuppress link in /v1/status are signed capabilities: anyone holding one can perform that one action on that one record, indefinitely. They grant nothing else and read nothing. If a link may have leaked, the suppression state itself is always inspectable and reversible from /v1/status, which requires your ingest token.
Your fingerprint salt is not in this list, because we do not have it.
4Where this runs
One application on Vercel, in Python, and one PostgreSQL database on Supabase. One scheduled job, hourly, which is what evaluates the detection rule. That is the entire production footprint.
Every page this system serves is rendered on the server. There is no browser-side database client anywhere, which is why no database key of any kind is published in a bundle — there is no bundle for one to be in. The marketing site loads no third-party scripts, no analytics, and no externally-hosted fonts.
5Who can reach the database
The application, over one connection string held as an environment variable, and one operator — the person who runs Cerberus. There is no customer-facing database access of any kind and no support tool that reads tenant data.
Row-level security is enabled and forced on every table, with no policies defined. That combination is deny-all: a role without explicit bypass privileges reads nothing, so a leaked or misconfigured lesser credential returns empty results rather than rows. Forcing it applies the same rule to the table owner.
The operator dashboard is read-only. It has no button that changes anything; deleting a customer, purging their data, or rotating a token all require a terminal, deliberately.
6What leaves the system
Two outbound destinations exist, and there are no others:
- Your Slack webhook, which receives four kinds of message and no others. A fan-out alert when a key is used from many places at once, carrying the key fingerprint, the counts that triggered it, and a link. A new-endpoint alert when a key starts calling an endpoint it has never used before from addresses it was not already using, carrying the fingerprint, the new endpoint, and its known set. Your own release lands on the servers the key already runs on, so it does not page you; that comparison is between two things Cerberus already holds and needs nothing new from you. This second method is built and tested but is not enabled for any account, and will not be switched on for yours without us saying so first — fan-out is the only detector currently live. A setup notice if your integration reports too few distinct client IPs to detect anything, which clears itself when you fix it. And a one-time confirmation, the first time we receive your traffic, with what we can see so far and when detection starts.
- Our own operations webhook, which receives a daily health summary. It carries tenant numbers, counts, and timestamps — not names, not fingerprints, not events. Its purpose is to notice that a customer has stopped sending data, which otherwise looks identical to a quiet week.
7Retention and deletion
Raw events are deleted thirty days after receipt; hourly aggregates after ninety. Automatically, on the hourly job, without anyone asking. Aggregates outlive events because detection reads aggregates — a shorter window would leave the rule running against nothing.
Until 19 August 2026 this section said the opposite, and said it deliberately: there was no retention job, and a page that claimed one would have been the easiest false sentence on it. The job and this paragraph shipped in the same commit.
Deletion is an operator action, and there are two of them. One deactivates an account and stops ingest while leaving history intact. The other erases a customer entirely — every event, every alert, every suppression, and the account row — and requires the account name to be typed to confirm, because it cannot be undone. Ask, at hello@cerberushq.dev, and it is done by hand.
8If there were a breach
This section is a commitment, not a mechanism. There is no automated incident process to describe, and describing one would be the kind of claim this page exists to avoid.
What is mechanical is the blast radius, and it is worth stating because the design chose it. An attacker holding the entire database would have: usage metadata, irreversible fingerprints of keys and IP addresses, and customers’ Slack webhook URLs. They would not have any API key, any prompt or response, any end-user IP address, or any fingerprint salt, because none of those are ever transmitted.
The commitment: you would be contacted directly, by a person, at the address on your account, with what happened and what was reachable. Revoking your Slack webhook and rotating your ingest token are both things you can do immediately and without waiting for us.
9Subprocessors
| Who | What for |
|---|---|
| Vercel | Runs the application and the hourly job. Holds the environment variables. |
| Supabase | Hosts the PostgreSQL database where the metadata and alerts live. |
That is the complete list. Slack is not on it: the webhook points at your own workspace, under your own agreement with Slack, and we are not a party to it.
10What does not exist yet
Named plainly, because a security review will ask and an evasive answer costs more than the gap does.
- No data processing agreement. A DPA is a contract between a controller and a processor, and a processor has to be a legal party. There is not one yet. This is the same blocker as terms of service, and it resolves the same way.
- No SOC 2, no ISO 27001. Neither has been started.
- No third-party penetration test. The system has been reviewed against its own threat model and has not been tested by anyone external.
Questions this does not answer: hello@cerberushq.dev.