Secrets
The pluggable Secrets-Resolver — resolve API keys / signing keys / encryption keys from env (default), an HTTP vault, or a custom backend, set once in app.config.
Secrets (API keys, signing keys, the field-encryption key) resolve through a pluggable backend installed once at boot. The default reads process.env; swap it for an HTTP vault or a custom resolver without touching the code that consumes secrets. Plugins (e.g. @voltro/plugin-governance's field encryption) read keys through this resolver, so the same wiring serves every consumer.
Configure in app.config.ts
export default {
type: 'api' as const,
name: 'api',
// 'env' (default) — read process.env. Omit `secrets` entirely for this.
secrets: 'env',
// OR an HTTP vault: GET <url>/<key> (or a whole-map fetch), bearer-authed.
// secrets: { backend: 'http', url: process.env.VAULT_URL!, token: process.env.VAULT_TOKEN, ttlMs: 60_000 },
// OR a custom backend (any { get(key) => Promise<string | undefined> }).
// secrets: customBackend,
}The same chain serves voltro dev and voltro serve — they can't drift. With no secrets field, the env backend is used.
Reading a secret
In framework code (and plugins) resolve a key through the runtime resolver — never hard-read process.env for something a vault might own:
import { resolveSecret, resolveSecretSync } from '@voltro/runtime'
const key = await resolveSecret('STRIPE_SECRET_KEY') // backend → env fallback
const sync = resolveSecretSync('SESSION_SECRET') // env-only fast path (sync)resolveSecret consults the active backend first, then falls back to process.env. resolveSecretSync is the synchronous env-only path for hot code that can't await.
Backends
secrets |
Resolution |
|---|---|
'env' (default) |
process.env[key] |
{ backend: 'http', url, token?, ttlMs? } |
GET <url>/<key> (bearer token); ttlMs caches results (cachedBackend). Falls back to env on miss. |
a SecretsBackend value |
your own { get(key) => Promise<string | undefined> } — wrap a cloud secrets manager, KMS, etc. |
httpSecretsBackend supports both a per-key GET and a whole-map prefetch; cachedBackend(backend, ttlMs) wraps any backend with a TTL cache.
Field encryption
The field-encryption key (for .encrypted() columns — see plugin-governance) resolves through this same backend. governancePlugin({ fieldEncryption: true }) reads the secret VOLTRO_FIELD_ENCRYPTION_KEY (override with fieldEncryption: { secretKey }); point secrets at your vault and the key never touches an env file.
Testing
setSecretsBackend(backend) installs a backend for a test; resetSecretsBackend() restores the env default. resolveSecretsBackend(config) is the pure resolver the boot path uses to turn the secrets config value into a backend.