Section VI · Security Encrypted Postgres backups Read the changelog
dbcrate

12 May 2026.
dbcrate is in beta. The threat model and the cryptography below describe the architecture as it ships today; the operational maturity is still being built out. Where the two diverge, this page says so.

1. The defining principle

A managed-backup company is only as trustworthy as the worst thing one of its engineers can do at 3 a.m. after a bad week. We took that observation seriously when we drew the architecture, and the result is a system in which the dbcrate company — including its own database, its own engineers, and its own infrastructure — cannot read a customer’s backup contents using its own resources.

Two architectural commitments make that possible:

  1. Backup bytes never enter our infrastructure. They flow from your agent to the storage destination you nominate, encrypted on the host before they leave it.
  2. The key that decrypts your backups is yours. Your organisation has one X25519 keypair, generated when the organisation is created. The public key is distributed to your agents over mTLS. The private key exists in two forms: an operational copy held by the control plane envelope-encrypted under a master key kept in a SOPS-encrypted env file (and decrypted only at deploy time), and an optional recovery copy that you can download in standard age format and keep offline.

Together they mean: an attacker who has only our object storage gets ciphertext. An attacker who has only our database snapshot gets ciphertext (the wrapped private key is useless without the master key, and the master key is not in the database). An attacker who has both the database and the master key has, by definition, root in our production deployment — the one threat we do not claim to defeat. Even then, historical backups produced by an agent that has not held a recovery key remain decryptable only by stealing them from your bucket separately.

2. Threat model, written down

We take a position on every plausible attacker. Listed in roughly increasing severity.

Threat What we do about it
Network observer between agent, control plane, and storage TLS on every connection; mutual TLS between agent and control plane; SFTP destinations enforce known-hosts pinning.
Object-storage compromise (attacker reads your bucket) Backup files are age v1 ciphertext, encrypted to your organisation’s X25519 recipient. Files alone, without your private key, yield no plaintext.
Stolen agent host The agent holds no backup-decrypting key. It encrypts forward to the org public key only. A compromised agent can affect future backups it produces; it cannot read the historical archive.
Insider read of the control-plane database The database holds encrypted credentials, an envelope-encrypted org private key, and metadata. Without the master key from the deployment environment, neither credentials nor backups decrypt.
Full control-plane compromise (master key + database) Acknowledged. We do not pretend to defeat this. The master key lives in a SOPS+age-encrypted env file, decrypted only at deploy time. It is the single thing whose compromise unlocks credentials and backups together. We minimise the surface where it is reachable, log every decryption, and design for fast credential rotation.
dbcrate is unreachable (outage, bankruptcy, hostile takeover) You can fetch a recovery copy of your organisation’s private key from the dashboard at any time, in standard age format, and decrypt your backups using off-the-shelf tools. With your bucket and your recovery key, you have everything you need.
3. Encryption, with the receipts

Backups are encrypted using age v1. We chose age instead of inventing a dbcrate-specific format on purpose: it is small, audited, well-specified, and supported by tools we did not write.

The chain, in one figure:

Key hierarchyFig. 1
   master key  (lives in SOPS-encrypted env file, decrypted at deploy time)
        │
        ▼ wraps
   org private key  (DB, encrypted)        org public key  (DB, plaintext)
        │                                          │
        │ unwraps                                  │ distributed via
        │  (only on customer-initiated             │  GET /agent/v1/config
        │   operations, audit-logged)              ▼
        │                                  per-backup DEK  (lives in
        │                                  the age file header in S3)
        │                                          │
        │                                          │ encrypts
        ▼                                          ▼
                          backup ciphertext  (lives in your bucket)

Some properties worth stating in writing:

4. Identity and transport

The agent authenticates to the control plane with mutual TLS. The enrolment flow is:

  1. An operator with dashboard access creates an enrolment token. The token is shown once and is single-use.
  2. The agent, on first start, generates a private key, builds a certificate signing request, and presents it together with the token to POST /agent/v1/enroll.
  3. The control plane returns a short-lived leaf certificate signed by an org-scoped intermediate. The agent persists the cert and the private key on its host, in /var/lib/dbcrate/identity/, mode 0700; the key file is mode 0600.
  4. From then on every agent request to the control plane is over mTLS with that certificate. The certificate is renewed automatically at ~80% of its lifetime via POST /agent/v1/certificate/renew.

There are no shared API keys. There is no inbound network surface on the agent — it listens on no ports, opens no listening sockets, runs no debug servers over TCP. All communication is outbound.

Compromised or retired agents are revoked through a signed certificate revocation list (CRL). Agents pull the CRL on a loop and halt if their own serial appears in it.

5. Credentials, in transit and at rest

Credentials — database passwords and storage keys — live in three places, none of them comfortably.

The control plane’s logging library has a credential redactor; the agent’s slog calls take structured fields, never interpolated strings; both repos run a lint check that fails the build when a known credential field name appears in a log call.

6. Storage isolation

Backups go to a destination you nominate — an S3-compatible bucket on AWS, Cloudflare R2, Backblaze B2, Hetzner Object Storage, Wasabi, Tigris, MinIO, or your own; or an SFTP host you operate. We do not own the bucket. We do not have a copy of the bucket. We have no path that writes backup bytes anywhere else.

A storage destination is validated end-to-end before it is saved: the control plane runs a list / write / read / delete probe to confirm the credentials work and the policies allow what the agent will need, and surfaces the result in the dashboard. If a probe fails, the destination is not saved.

Retention deletion is performed by the control plane against your storage destination directly, on the schedule and rules you set. Agents have no delete code path — the agent binary cannot, even if a compromised control plane told it to, delete a backup file.

7. The audit log

Every consequential action in the control plane is recorded in an append-only audit table:

Audit entries are written by a single helper (dbcrate.core.audit.record) and the schema rejects updates and deletes at the application layer. The table is read by the dashboard’s audit view and is part of every relevant export.

8. How we build the software

The architecture is one half of the story; the practice is the other. The practices we hold to, in the form they exist today:

We do not yet hold any third-party security certifications (SOC 2, ISO 27001, similar). Pursuing one is on the company roadmap. The dashboard audit log and the design choices above are the substantive part of what such an audit would attest to; the formal attestation will follow when it is honest to seek one.

9. What we cannot defeat, and what we ask of you

A short list of failure modes the architecture leaves to the customer to manage. These are not weasel words; they are the points at which the customer has to do something the system cannot do for them.

If any of these are intolerable for your use case — for example, if you cannot reasonably hold a recovery key offline — tell us, and let us help you find an architecture you can live with.

10. Reporting a vulnerability

If you believe you have found a security issue in dbcrate, send a report to [email protected]. The address has a published mailbox; we read it every working day.

What we ask of you:

What you can expect from us:

A PGP key for the security mailbox will be published at /.well-known/security.txt alongside the disclosure policy.

11. Contact

Security disclosures: [email protected]. Privacy questions: [email protected]. Anything else: [email protected].