Skip to main content
Secrets you save through the dashboard or the admin API are stored in GoModel’s database. Set GOMODEL_ENCRYPTION_KEY and they are encrypted before they are written, so a database file, dump, backup, or replica no longer holds them in plaintext. Encrypted fields: Credentials declared in environment variables or config.yaml are never written to the database, so they are not affected. Default: unset. Secrets are stored in plaintext, and GoModel logs one warning at startup when an enabled feature (providers, and MCP or guardrails when they are on) loads a plaintext secret.

Enable it

Generate a key and pass it to every instance that shares the database:
An empty value leaves encryption off. Any other string works, but the key-encryption key is derived from it, so a short or guessable value weakens the encryption: use 32 random bytes as above. Deliver it the way you deliver other secrets, from a secret manager at runtime, not from a committed file. A secret reference such as GOMODEL_ENCRYPTION_KEY=${file:/run/secrets/gomodel-encryption-key} works too.
Losing the key makes the encrypted secrets unrecoverable. Store it as carefully as the database backups it protects, and keep it separate from them. Without the key, GoModel refuses to start against a database that holds encrypted secrets.

Existing plaintext secrets

Enabling the key does not rewrite anything. Plaintext values keep working and are encrypted the next time each entry is saved. To encrypt everything at once, run:
It uses the same configuration and environment as the gateway, prints counts per table (never values), and is safe to run repeatedly and while the gateway is running. Each rewrite only succeeds if the entry’s secrets are still what the command read, so an edit or delete made during the run is never overwritten; the command re-reads the entry instead. An entry that keeps changing is reported as skipped, and the next run picks it up.

Rotate the key

To change GOMODEL_ENCRYPTION_KEY, start with the new key and the old one in GOMODEL_ENCRYPTION_KEY_PREVIOUS:
At startup GoModel re-wraps the database’s data key with the new key. Stored secrets are not rewritten, so this is instant. Once every instance runs with the new key, remove GOMODEL_ENCRYPTION_KEY_PREVIOUS. To replace the data key itself, for example after a suspected leak of a database copy together with the old key, run:
This creates a new data key and re-encrypts every secret with it. Values under older data keys stay readable throughout. A running gateway checks which data key is active before it saves a secret, so it switches to the new key without a restart, and checks again after the save: a save that was already in flight when the key changed re-encrypts itself with the new key. If that second check cannot reach the key store, the secret is still saved, but the save returns an error asking you to save it again or run gomodel secrets reencrypt.

How it works

Each database gets a random 256-bit data key, stored only in wrapped form in the encryption_keys table. The key that wraps it is derived from GOMODEL_ENCRYPTION_KEY with Argon2id. Secret values are encrypted with AES-256-GCM and stored as enc:v1:<key-id>:<ciphertext>; the row and field are bound into the ciphertext, so a value copied into another row or field fails to decrypt instead of being used there. The admin API is unchanged: secrets are still masked in responses. Encryption at rest protects copies of the database. It does not protect against someone who can read the gateway’s environment or memory, and it does not replace restricting access to the database and its backups.

Custom distributions

A distribution built on GoModel can wrap the data key with a KMS instead of GOMODEL_ENCRYPTION_KEY by calling LoadResult.SetKeyWrapper with a config.KeyWrapper from run.Options.SetupConfig. On the first start with a new wrapper, keep GOMODEL_ENCRYPTION_KEY set once so the existing data key can be moved to it. To move from one wrapper to another, pass the old one as a previous wrapper: SetKeyWrapper(next, previous).
Last modified on October 4, 2026