August 27, 2026

How we store your API keys

We used to encrypt your keys with a secret that shipped inside our JavaScript bundle. Here is what we replaced it with, and why we are telling you.

This is a note about a mistake we made. You hand us a credential, so you should know how we hold it, including the part where we held it badly.

What was wrong

Our first design encrypted your API keys in the browser, using a key derived from a value that shipped inside our public JavaScript bundle. A secret that ships in a public bundle isn't a secret. Anyone with a database dump and our bundle could have decrypted every key we stored.

What we replaced it with

Keys are now sealed on our servers using envelope encryption. Each stored blob gets its own freshly generated data key, and that data key is itself wrapped by a master key that lives only in the server environment and never goes near a browser.
  • The encryption key isn't in our client code any more, because the encryption doesn't happen in the client any more.
  • Rotating the master key just re-wraps the per-blob keys. We never have to decrypt your key material back to plaintext to do it.
  • If the master key is missing, the system fails closed. It won't quietly fall back to something weaker.
The honest detail: keys stored under the old scheme are still readable, so we can upgrade them in place instead of silently dropping them and breaking your generations. They get migrated as they're used, and by a backfill. Anything you add today is written with the new scheme.

The rule we should have followed

If a secret has to be readable by code running on someone else's machine, it was never a secret. That sounds obvious written down, which is precisely why we thought it was worth writing down.