Skip to main content
Before you start
  • Your credentials are encrypted. Your data is not. The vault holds provider tokens and passwords. The rows Postern syncs are ordinary Postgres columns.
  • Either you hold the master key or Postern does. Which one it is decides half of this page.
  • The key file is the whole secret. There is no passphrase and no escrow. Anything that can read that file can open the vault.
  • docker compose down -v destroys it. Every credential encrypted under that key becomes permanently unopenable, and no recovery path exists.

The credential vault

Postern encrypts every password a source gave you before it reaches the database. It never hands a source password to an agent, on any path. In the vault: OAuth access and refresh tokens, app-specific passwords, SimpleFIN access URLs, Home Assistant tokens and Plaid item tokens. Postern also encrypts the client secrets of the provider apps you register yourself. They live in one secrets table. A credential row holds only a pointer of the form sec_<uuid>, never the secret. Postern never writes a secret into a data row, and no route ever returns one. A record encrypted for one purpose does not open as another, and a swapped pointer fails rather than decrypts. SECURITY.md carries the exact algorithm parameters and the rest of the cryptographic detail. There is no password on the vault. The master key is 32 raw bytes. Either Postern generates them on first use, or you supply them in PCI_MASTER_KEY as base64 of exactly 32 bytes. Postern refuses a value that decodes to any other length outright, and never pads or truncates one. There is no passphrase, no key derivation and no escrow. The key file is the secret, and anything that can read it can open the vault. Postern generates the key the first time it encrypts a credential, not at first boot. A Postern that has connected nothing has no key yet. Under Docker the pci_master_key volume mounts at /app/.pci, so a generated key file is /app/.pci/master.key — inside that volume, never in your checkout.
Postern looks an encrypted record up by the label of the key that encrypted it, not by the key. So when Postern refuses to start over the vault, read PCI_MASTER_KEY_ID in .env before you reach for a backup. The key can be perfectly intact with only its label changed: set, unset, or edited. The refusal message names the label it wanted. Set PCI_MASTER_KEY_ID back to that label, then run docker compose up -d app. Nothing needs to be restored.

A disconnect removes the credential, not the data

Postern sets the connection and its credential to revoked, so nothing schedules another sync. Then it deletes the encrypted access token, the refresh token and the stored client secret from the vault. The cached rows are a third thing, and Postern leaves them alone.

Rotating the master key

Rotation is offline. Postern re-encrypts every record under the new key, in place under the same pointers, so nothing that points at a secret has to change. An interrupted run resumes, because Postern derives the new key’s label from the key bytes and the same command skips the records it already re-encrypted.
Never rotate between the moment you copy the key and the moment you copy the database. After a rotation an older database copy needs the older key. Keep the pre-rotation backup until every connection reads ok again.
PCI_MASTER_KEY_ID defaults to the literal env, so a key supplied without its label matches none of the re-encrypted records and Postern refuses to boot. docs/runbooks/key-rotation.md in the repository is the full procedure. It covers the source-checkout form too.

What the vault does not cover

Encrypted by Postern: the credentials above, and nothing else. Not encrypted by Postern: everything it syncs. Your transactions, mail envelopes, calendar events, contacts and health samples are ordinary columns in your Postgres, readable by anything that can read the database. What protects them is database access control and whatever disk encryption the machine provides. Postern ships nothing that encrypts your data at rest, and this page carries no procedure for it. Full-disk encryption on the machine Postern runs on is the usual answer: FileVault on macOS, LUKS on Linux, BitLocker on Windows. Each vendor documents its own. One narrower fact cuts the other way. The mail table has no column for a message body or an attachment. Postern stores who sent it, the subject, the dates and the preview line. It never stores the message text. Postern fetches a body from the provider when an agent asks for that one message.

Losing the vault key

docker compose down -v deletes the named volumes, and pci_master_key is one of them. Every credential encrypted under that key becomes permanently unopenable. The ciphertext is all that survives, and no recovery path exists — not in this project, not anywhere. docker compose down without the -v removes the containers and leaves the volumes alone.
If Postern cannot open the vault at start-up, it refuses to start rather than start broken. The migrations run first. Then, before Postern serves any port, it opens one encrypted record per distinct key label among the credentials its live connectors need. If any of those fails, Postern refuses to start. It names the label, the count and the first pointer that failed, and it reports that it changed nothing. No environment variable, flag or setting skips that check. The check is deliberately narrow, so a real read is still the verification that counts. A wrong key restores cleanly and then decrypts nothing.

The backup banner, and how to stop it

Postern prints a banner headed VAULT KEY IS NOT BACKED UP on its own log at every start. Three things have to be true for it: Postern generated the key itself, the key file is there, and no marker file sits beside it. That is the whole check. Postern never looks at your backup. It never reads the marker’s contents, and it never hashes or fingerprints the key. The banner’s last line tells you to acknowledge in the Console. No such control ships today — no button, no page, no route. The function that would write the marker exists in Postern’s code, and nothing calls it. The banner is a reminder, not an error, and Postern starts anyway. Take your copy of the key first. Back up, export and erase is the procedure. Then stop the banner one of these two ways. Write the marker yourself. The marker is a file beside the key. Its name is the key’s own filename plus .backed-up, so on a stock Docker install it is /app/.pci/master.key.backed-up. In the folder that holds docker-compose.yml, run:
Postern only checks that the file is there. Write the date into it anyway, so a later you knows when you took the copy. If you set PCI_MASTER_KEY_PATH, use that path plus .backed-up instead. The marker sits in the same volume as the key on purpose. docker compose down -v takes both, and a marker that outlived its key would be a lie.
Hold the key yourself — but only on a Postern that has connected nothing yet, or through the rotation above. Set PCI_MASTER_KEY in .env to a base64 32-byte value you keep, then restart. Postern then treats the key as yours: no marker, and the banner never prints again. Point a new key at a vault an old key filled, and Postern refuses to start.
The banner comes back after every restore. A restore seeds the key into a new, empty volume, and the marker was in the old one. Write it again.

Behaviours you will meet

/api/export answers on this computer only, like every other admin route — what protects each route group.

Confirm it works

  • docker compose ps shows app as healthy, so the vault check passed at start-up.
  • A source you connected reads ok in the Console, which is a real decryption rather than the narrow start-up check.
  • If you took your own copy of the key, the marker file sits beside it and the VAULT KEY IS NOT BACKED UP banner no longer prints at start.

If something went wrong

What you have now

Your credentials are encrypted and your data is not. The master key is 32 raw bytes with no passphrase behind it, so your own copy of that file is the only recovery path there will ever be — and docker compose down -v ends it for good.

Next

Back up, export and erase

the copy to take, the order to take it in, and what erase leaves behind

Provider sign-ins

what each provider needs, and the three credentials that behave like passwords