Security & compliance
This page documents PerSQL’s current security posture: controls in place today, items on the roadmap, and the status of formal certifications. It is updated as new controls ship.
For security questions not answered here, or to report a vulnerability, contact security@persql.com.
Authentication
Section titled “Authentication”- Console uses Better Auth with GitHub OAuth, Google OAuth, and email/password. Sessions are HTTP-only cookies, scoped to the console hostname.
- API (
/v1/*) uses bearer tokens (Authorization: Bearer psql_live_…for production tokens,psql_test_…for tests, andpsql_cli_…for thepersqlCLI). Tokens are namespace-scoped, can be set read-only, and are revocable from the console. - Token validation is KV-cached for 60s to keep D1 reads off the hot path. Revocation propagates within that TTL.
Tenant isolation
Section titled “Tenant isolation”Every database is its own Cloudflare Durable Object with embedded
SQLite. There is no shared connection pool, no shared SQLite engine,
no shared cache between tenants — when you query database A, none
of the bytes in database B are reachable from your code path. Even
the idempotency cache is namespaced by token and DB ID.
The control plane (users, namespaces, members, the database registry)
lives in a single Cloudflare D1 instance. Row-level access is enforced
in apps/api middleware — a request for acme/orders is rejected at
auth time if the bearer token’s namespace isn’t acme.
Encryption
Section titled “Encryption”- In transit: TLS 1.3, terminated at Cloudflare’s edge. No plaintext
HTTP —
api.persql.com,console.persql.com,*.persql.com, and customer SaaS hostnames all redirect. - At rest: Cloudflare-managed encryption for Durable Object storage and D1. We don’t run our own KMS.
- Token storage: bearer tokens are stored hashed (SHA-256) in D1. We never log raw tokens — the analytics pipeline stores only the token’s ID.
Data residency
Section titled “Data residency”- Region pinning. When you create a database, you can pin its
Durable Object to a Cloudflare location group:
wnam,enam,weur,eeur,apac,oc,me,afr. The DO’s home is fixed at init — moving it later is not currently supported. auto(the default) lets Cloudflare place the DO at first request.- Control plane (D1) is global; it stores no application data — only metadata (user IDs, namespace slugs, database registry rows, schedule definitions, audit metadata).
If you have a hard residency requirement (e.g. EU-only), pin every
database to weur and email us so we can flag the namespace.
What we log
Section titled “What we log”- Query analytics — namespace ID, database ID, token ID, status, rows-read, rows-written, duration. No SQL text and no parameters. Used for billing and the customer “Insights” view.
- Slow query log (per database) — first 4 KB of SQL text, redacted parameters, duration. Visible only to namespace members.
- Audit log (per namespace) — DDL changes, schedule runs, membership changes, custom hostname events. Visible only to namespace members.
- Better Auth events — sign-in, sign-out, OAuth provider name. No password material.
- Cloudflare access logs — handled by Cloudflare per their DPA.
We do not sell logs, query data, or any other customer data. We do not feed customer query data into third-party model training.
Backups & recovery
Section titled “Backups & recovery”- Point-in-time recovery (30 days). Every database gets continuous PITR via the underlying Cloudflare SQLite engine’s 30-day rolling bookmark history — every committed write is recoverable. No schedule, no cron, no plan tier. See Backups.
- Labeled snapshots. Take a named snapshot before a risky operation and restore to that label later. The label is just a pointer into the same 30-day window.
- Long-term archives. For retention past 30 days, take a manual R2 archive (or call the archive endpoint from CI). Archives stay until you delete them. See Archives.
- Restore. Owner/admin can restore in place from any bookmark or labeled snapshot, or fork a snapshot into a new database.
- Forking. Any database can be forked into an independent copy at any time — handy for staging or what-if work. See Forking.
Members & roles
Section titled “Members & roles”Roles are: owner → admin → member. The owner is the namespace
creator and is the only role that can transfer ownership or delete
the namespace. Roles are checked in middleware on every API call;
see Members & roles for what each can do.
Database-level overrides are supported — you can grant a member read-only on production while leaving them admin on staging.
Compliance posture
Section titled “Compliance posture”- SOC 2 Type I — in progress. We’re operating against the SOC 2 controls (audit log, change management, access reviews, vendor review) but have not yet completed a Type I audit. Expect a target date in the second half of 2026.
- GDPR. We will sign a DPA on request — email security@persql.com with your entity name and we’ll send the current version. PerSQL acts as a processor; the customer is the controller. Subprocessors are listed below.
- HIPAA. Not supported. Don’t put PHI on PerSQL today.
- PCI-DSS. Not supported. Don’t put unencrypted cardholder data on PerSQL today; use a tokenization vault.
- CCPA / CPRA. We follow GDPR-equivalent processes for California residents — same DPA covers both.
Subprocessors
Section titled “Subprocessors”| Subprocessor | Purpose | Data |
|---|---|---|
| Cloudflare (Workers, Durable Objects, D1, R2, KV, Pages, Access, Analytics Engine) | Compute, storage, DNS, edge | All customer data, metadata, logs |
| Resend | Transactional email (alerts, invites) | Email addresses, member names |
| Stripe | Billing | Email, namespace, payment method (held by Stripe) |
Better Auth (self-hosted in apps/api) | Auth library — runs in our worker | Sessions, OAuth tokens |
| Pulumi (state on R2) | Infrastructure-as-code | No customer data, only infra resource IDs |
Vulnerability disclosure
Section titled “Vulnerability disclosure”If you’ve found a vulnerability:
- Email security@persql.com with details. PGP key on request.
- Don’t post publicly until we’ve had time to fix it.
- We’ll acknowledge within 2 business days and aim to fix high-severity issues within 14 days.
We don’t have a bug bounty program yet, but we’ll happily credit researchers in a security advisory and send you swag.
Incident response
Section titled “Incident response”Public status: status.persql.com.
Severe incidents (data exposure, prolonged outage > 1 h) trigger an email to namespace owners within 24 h with a postmortem at the status page within 5 business days. No automated SLA credits today — this is a pre-revenue posture and we’ll formalise it with the SOC 2 work.