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.
How it works
Section titled “How it works”- Algorithm: AES-256-GCM (authenticated encryption — tamper-evident).
- Key: 32 bytes, from the
VAULTBASE_ENCRYPTION_KEYenv 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: truein the field’s options. - Transparent: the API returns plaintext on read, encrypts on write — clients don’t see the difference.
1. Generate a key (optional)
Section titled “1. Generate a key (optional)”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
2. Persist the key
Section titled “2. Persist the key”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.
VAULTBASE_ENCRYPTION_KEY=Kx2Zb...random-bytes...=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.
3. Mark fields encrypted
Section titled “3. Mark fields encrypted”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).
What you see in the API
Section titled “What you see in the API”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_..." } } ← plaintextWhat’s actually in data.db
Section titled “What’s actually in data.db”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
nulland the server log names the record and field), - correlate two ciphertexts of the same plaintext (random IV per write).
Filtering on encrypted fields
Section titled “Filtering on encrypted fields”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).
Key rotation
Section titled “Key rotation”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:
- Read every encrypted record via the API (decrypts with old key).
- Note your secret settings, stop the server, and delete their rows from
vaultbase_settings(otherwise the new key is refused at startup). - Restart with the new
VAULTBASE_ENCRYPTION_KEY. - 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.
Backups
Section titled “Backups”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-keyfile).
Treat the key like a TLS private key — back it up separately from the database. Losing the key = losing the data.
Performance
Section titled “Performance”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.