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.