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 [email protected].
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 (5 min) to keep D1 reads off the hot path. Revoking a token takes effect immediately — revocation is enforced on every request, including cache hits, so it is not bounded by the cache TTL.
Tenant isolation
Section titled “Tenant isolation”Every database is a fully isolated SQLite instance on the edge.
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.
PerSQL’s metadata store (users, namespaces, members, the database
registry) lives separately from tenant data. 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.2+, 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 database 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 it
to a location group:
wnam,enam,sam,weur,eeur,apac,oc,afr,me. The database’s home region is fixed at init — moving it later is not currently supported. auto(the default) lets the edge place the database at first request.- Metadata store (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) — the SQL statement text (truncated to ~1.5 KB) and timing. Bound parameters are never captured — only the parameterized statement is stored. (Literal values you inline into the SQL string are stored as written; parameterize to keep them out of the log.) 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’s schema can be forked into an independent copy at any time (tables, indexes, and views — no rows) — 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 — not yet underway. PerSQL runs on Cloudflare infrastructure, which is independently SOC 2 Type II and ISO 27001 certified; we have not begun an audit of our own application-layer controls and will pursue one as enterprise demand warrants. The controls in place today are documented above — isolation, hashed tokens, per-namespace audit logging.
- GDPR. We will sign a DPA on request — email [email protected] 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 eligible. We do not sign Business Associate Agreements (BAAs). Do not store protected health information (PHI) in PerSQL.
- PCI-DSS. Out of scope. Payments for PerSQL run through Stripe, so cardholder data never reaches our systems; PerSQL itself is not PCI-DSS assessed, so do not store cardholder data (PANs) in your databases.
- 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, Workers AI) | Compute, storage, DNS, edge, NL→SQL | All customer data, metadata, logs |
| Resend | Transactional email (alerts, invites) | Email addresses, member names |
| Stripe | Billing | Email, namespace, payment method (held by Stripe) |
Authentication runs on Better Auth self-hosted inside our own worker — no third-party identity provider sees your sessions or credentials. Infrastructure is managed with Pulumi (state on R2); that state holds only resource IDs, never customer data. Neither is a subprocessor of customer personal data.
Vulnerability disclosure
Section titled “Vulnerability disclosure”If you’ve found a vulnerability:
- Email [email protected] 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.