Security model
Confirm sits between your agents and their most dangerous actions, so its own security model has to be legible. This page is the honest version — what we store, what we sign, and what the trust boundaries are.
Credentials at rest
- API keys (
cfm_live_…) are stored as SHA-256 hashes. The plaintext exists only in the moment of creation, on your screen. A key prefix is kept for dashboard display; revocation is immediate. - Magic-link tokens are 256 bits from a CSPRNG, also stored only as hashes. The plaintext lives solely in the email link. A database leak therefore exposes neither the ability to create requests nor the ability to approve them.
- Webhook secrets (
whsec_…) are per-endpoint and shown once.
Approval authentication
Approvers authenticate by possession: the unguessable token in the magic link. Each token is bound to exactly one request, is single-use in effect (terminal states can't be re-resolved), and dies at the request's TTL. Every resolution additionally records the approver's email identity, IP address, and user agent — so possession is accountable, not anonymous.
THREAT MODEL
Single-resolution guarantee
Approve/reject transitions are guarded compare-and-set updates: they succeed only from PENDING. Two people clicking simultaneously, a double-submit, or a decision racing the expiry sweep produce exactly one verdict; all later attempts receive an "already resolved" error. There is no code path that flips a terminal state.
Webhook integrity
Every delivery is signed with HMAC-SHA256 over a timestamped payload using your endpoint's secret, sent as X-Confirm-Signature: t=<ts>,v1=<hex>. Verifying the signature proves origin and integrity; enforcing a timestamp tolerance blocks replays. Endpoints must be HTTPS in production.
Audit trail
The audit trail is structural, not a feature flag: requests, resolutions, and webhook delivery attempts are written as append-only rows. What the agent proposed, what the human changed it to, who decided, when, from where, and what your systems were told — each is a separate immutable record. Nothing in the product mutates history.
Tenancy & transport
- Every resource is scoped to a workspace; API keys can only read and create within their own. Cross-tenant reads return 404, not 403 — existence is not leaked.
- All traffic is TLS. Data is encrypted at rest by the underlying Postgres provider.
- Approval emails contain the summary you provide and the link — put secrets in
payload(shown only on the review page), not insummary.
Reporting
Found something? Email security@confirm.dev. We commit to acknowledging reports within one business day.