Skip to content

Encrypted fields

AES-GCM at rest for individual field values — opt-in per field, key from env.

Vaultbase can encrypt the value of individual fields at rest in SQLite, without requiring full-database encryption. Useful for storing API keys, PII, secrets, or anything that should be protected even if data.db leaks.

  • Algorithm: AES-256-GCM (authenticated encryption — tamper-evident).
  • Key: 32 bytes, from the VAULTBASE_ENCRYPTION_KEY env var. When it’s unset, Vaultbase generates one on first start into <dataDir>/.encryption-key (mode 0600) and uses that. The same key encrypts secret settings (SMTP password, OAuth client secrets, storage keys); any stored in plaintext are encrypted at startup.
  • Format: each value is stored as vbenc:1:<base64 iv>:<base64 ciphertext> (the 16-byte GCM auth tag is appended to the ciphertext).
  • Scope: opt-in per field via encrypted: true in the field’s options.
  • Transparent: the API returns plaintext on read, encrypts on write — clients don’t see the difference.
Terminal window
openssl rand -base64 32
# → e.g. "Kx2Zb...lots-of-random-bytes...="

Set it as VAULTBASE_ENCRYPTION_KEY. The value can be:

  • a base64 string (recommended; auto-decoded to 32 bytes), or
  • a hex string (64 chars), or
  • any 32-character ASCII string

Add it to your env file / systemd unit / Docker compose / k8s secret. Never lose this key — there’s no recovery. Vaultbase doesn’t store a key you set this way (it only writes .encryption-key when the env var is unset). If you restart with a different key, encrypted values can’t be read: secret settings stop the server from starting (see Key rotation), and encrypted record fields read as null.

.env
VAULTBASE_ENCRYPTION_KEY=Kx2Zb...random-bytes...=
docker-compose.yml
environment:
VAULTBASE_ENCRYPTION_KEY: ${VAULTBASE_ENCRYPTION_KEY}

There is always a key — yours, or the generated one — so encrypted fields never fall back to plaintext.

In the schema editor, expand the field options panel and toggle Encrypted. Or via the API:

PATCH /api/v1/collections/<id>
{
"fields": [
{ "name": "stripe_secret_key", "type": "text",
"options": { "encrypted": true } },
{ "name": "metadata", "type": "json",
"options": { "encrypted": true } }
]
}

Encryptable types: text, email, url, json. Other types either don’t make sense (bool, number, date) or have their own protection (password is Argon2-hashed).

POST /api/v1/secrets
{ "name": "stripe", "stripe_secret_key": "sk_live_..." }
→ { "data": { "id": "...", "stripe_secret_key": "sk_live_..." } } ← plaintext
GET /api/v1/secrets/<id>
→ { "data": { "id": "...", "stripe_secret_key": "sk_live_..." } } ← plaintext
SELECT stripe_secret_key FROM vb_secrets;
-- vbenc:1:99CZg7dSlb14AFhG:rFZTtsW3wLNh... (iv:ciphertext+auth-tag, base64-encoded)

A SQLite dump or filesystem leak yields ciphertext only. With AES-GCM, an attacker can’t:

  • decrypt without the key,
  • tamper undetected (on an auth-tag mismatch the field reads as null and the server log names the record and field),
  • correlate two ciphertexts of the same plaintext (random IV per write).

Equality and substring filters on encrypted columns don’t work — the ciphertext is randomized per write, so the same plaintext won’t compare equal in SQL. If you need to filter, store a hash in a separate non-encrypted field (e.g. SHA-256 of the email for lookup, plus the encrypted email for display).

There’s no built-in rotation today. Vaultbase refuses to start when the key doesn’t decrypt the stored secret settings (SMTP password, OAuth client secrets, storage keys). The error names the fix: set VAULTBASE_ENCRYPTION_KEY back to the original key, or restore the original .encryption-key. To rotate anyway:

  1. Read every encrypted record via the API (decrypts with old key).
  2. Note your secret settings, stop the server, and delete their rows from vaultbase_settings (otherwise the new key is refused at startup).
  3. Restart with the new VAULTBASE_ENCRYPTION_KEY.
  4. Re-enter the secret settings and write each record back (encrypts with the new key). Until then, old encrypted fields read as null.

Tracked as a follow-up. For high-stakes deploys, accept this manual step or roll your own re-encrypt script via Hooks.

Backed-up data.db files contain ciphertext. To restore on a new host you need both:

  • The DB snapshot, and
  • The key that was active when those values were written (VAULTBASE_ENCRYPTION_KEY, or the .encryption-key file).

Treat the key like a TLS private key — back it up separately from the database. Losing the key = losing the data.

AES-GCM is ~1 GB/s on a modern CPU; for typical record sizes the overhead is negligible (microseconds per row). Decryption happens once per row read; if you have a hot, large table where most fields don’t need encryption, leave them unencrypted and only flag the sensitive columns.