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 -vdestroys 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 onesecrets 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.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
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 headedVAULT 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:
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.
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 psshowsappashealthy, so the vault check passed at start-up.- A source you connected reads
okin 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 UPbanner 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 — anddocker 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