Overview
Protected knowledge — declare secret and config contracts, resolve values at boot, read Redacted credentials through fx.vault.
Vault is how your backend holds secrets and configuration safely. Declare a contract
(vault.secret, vault.config), resolve the value through the boot chain, and read it inside
Flows as Redacted — never as a plain string in logs or responses.
For developers wiring Stripe keys, SMTP URLs, and feature flags on okengine — contracts first, values only at the provider edge.
The one rule
Contracts, never values. Application code declares vault.secret("STRIPE_KEY") (or
vault.config). Cleartext crosses only at .reveal() on the credential boundary — never in
console.log, JSON, or the HTTP envelope.
Resolution chain — first hit wins
miss every layer → VaultBootErrorSTRIPE_KEY- 1
driverBuilt-in vault / managed bag - 2
process.envReal env (CI, hosting) - 3
.env.localLocal overrides (gitignored) - 4
dev-fallbackdev: on the contract — never in prod - →
Resolving chain…
Smallest Example
Declare a secret contract
import { vault } from "okengine";
export const webhookSecret = vault.secret("APP_WEBHOOK_SECRET", {
description: "HMAC secret for outbound note webhooks",
// Dev-only fallback — never used in prod boot
dev: "dev-webhook-secret-change-me",
});Read it in a Flow
import { on, flow, http } from "okengine";
import { webhookSecret } from "@/core/vault";
import { notesMutate } from "@/core/gate";
export const create = on(
http.post().gate(notesMutate),
flow({
do: async (input, fx) => {
// Touches the contract → effects.secrets; reveal only at an SDK edge
await fx.vault.get(webhookSecret);
return fx.json.create({ id: fx.id(), title: input.title });
},
}),
);The compiler stamps secrets: ["APP_WEBHOOK_SECRET"] on the Flow’s effects.
Boot and call
# Missing value → VaultBootError lists every gap at once
oke vault set APP_WEBHOOK_SECRET
curl -X POST http://localhost:6530/notes \
-H "accept: application/json" \
-H "content-type: application/json" \
-d '{"title":"hello"}'fx.log / String(key) / JSON.stringify(key) all show [redacted] — never the HMAC material.
Progressive Patterns
From a fingerprinted secret to cleartext config, plain env helpers, and Docker-local fallbacks:
Fingerprinted contract — Console shows a fingerprint, never the value:
import { vault } from "okengine";
import { z } from "zod";
export const stripeKey = vault.secret("STRIPE_KEY", {
description: "Stripe secret API key",
schema: z.string().startsWith("sk_"),
rotate: "90d",
// Optional local fallback when drivers.vault.dev allows it
dev: "sk_test_local",
});vault("STRIPE_KEY", opts) is the same as vault.secret.
Declaration Reference
| Declaration | Signature | Purpose |
|---|---|---|
vault.secret | vault.secret(name, options?) | Fingerprinted secret contract |
vault | vault(name, options?) | Alias for vault.secret |
vault.config | vault.config(name, options?) | Cleartext config contract |
vault.fromDocker | vault.fromDocker(role) | Dev fallback marker for an image role |
vault.env | vault.env / .required / .int / .bool / .json | Sync process env — no contract |
| Option | Type | Default | Meaning |
|---|---|---|---|
description | string | — | Shown in boot-gap listings and Console |
rotate | string | omit ≈ "never" | Cadence hint ("90d") or "never" — Console posture, not auto-rotate |
schema | Schema | — | Declared shape (Zod / Standard Schema) for docs and tooling |
dev | string | — | Dev-only fallback; never used in prod boot |
sensitive | boolean | true secret · false config | Whether cleartext must never leave the runtime |
perTenant | boolean | true when tenancy on | Resolve {tenantId}/{name} at request time; not boot-gap fatal |
Resolution Chain
Detailed section
If you only need “declare and fx.vault.get”, the Smallest Example is enough. This section is how
boot finds a value — first hit wins.
Order (Console labels match these source ids):
- driver — built-in encrypted store, managed bag, or
env/memorydriver - process.env — real environment (CI, hosting)
.env.local— local overrides (gitignored)- dev-fallback —
dev:on the contract (dev boot only)
Miss every layer → VaultBootError listing all gaps in one pass (including
vault.env.required names):
vault boot failed — 2 missing secret(s):
- STRIPE_KEY: Payments gateway key
- DATABASE_URL: Primary SQL URLConsequence: fix every listed name before traffic — boot does not take a half-configured app.
See Secrets for contracts and Config
for cleartext + vault.env.
Redacted until reveal
Redacted until you reveal
fx.vault.get(secret) → Redactedgetlogjsonrevealhold Redactedawait fx.vault.get(stripeKey)fx.log.info(key)·JSON.stringify({ key })·
shows
[redacted]fx.log, String(), and JSON.stringify never see the value — nested Redacted included.
reveal at boundarykey.reveal()new Stripe(key.reveal())·fx.log after reveal·
shows
sk_test_…One explicit call at the Stripe (or SMTP, or SDK) edge — the credential crosses only there.
fx.vault.get(contract) returns Promise<Redacted<string>>. Printing, logging, and JSON all
yield [redacted]. Call .reveal() only at the Stripe / SMTP / SDK edge.
Loaded secret substrings are also scrubbed from fx.log even when you pass cleartext by mistake
(mask token [redacted:secret]).
fx.vault surface
| Method | Needs backend | Meaning |
|---|---|---|
get(contract) | No | Redacted<string> — every app has this |
set(path, value, opts?) | Yes (vault) | New version; optional ttlMs / metadata |
rotate(path, value) | Yes (vault) | New version under a fresh data key |
delete(path) | Yes (vault) | Crypto-shred; returns whether anything went |
list(prefix?) | Yes (vault) | Paths only — never values |
status() | Yes (vault) | { sealed, initialized, backend } |
Without a bound encrypted backend, mutations throw:
fx.vault.set needs a bound Vault backend — configure the vault element (drivers.vault = "vault") …Dry-run refuses writes rather than mutate a live secret (DryRunWriteIsolationError).
Per-environment drivers
Defaults from DRIVER_DEFAULTS.vault — pin overrides in oke.config.ts:
import { defineConfig } from "okengine/config";
export default defineConfig({
drivers: {
// Default vault.dev is "env"; Docker-first starters pin built-in:
vault: {
dev: "vault",
// test: "memory" (default)
// prod: "vault" (default)
},
},
});| Driver | Runs as | Best for |
|---|---|---|
env | Process / dotenv bag | Simple local / CI without SQL vault tables |
vault | Built-in AES-256-GCM in Postgres | Docker-first apps; oke vault init / unseal |
memory | In-process map | Tests |
managed | Remote provider bag | AWS / Azure / GCP / Doppler / 1Password |
create-oke Notes starters pin vault.dev: "vault" and declare stack + app contracts in
src/vault.ts (vault.secret / vault.config). Console Vault lists those contracts —
values still resolve from .env.local, process env, oke vault set, or dev: /
vault.fromDocker fallbacks. Pass oke({ secrets: NOTES_VAULT }) so configs resolve
(only vault.secret auto-registers).
Managed provider ids: aws-secrets-manager · azure-key-vault · gcp-secret-manager ·
doppler · 1password. Env knobs: Environment variables.
The Capabilities of Vault
Secrets
Fingerprinted vault.secret contracts, Redacted reads, boot gaps.
Config
vault.config cleartext settings and vault.env helpers.
Key Rotation
Version rotate, master-key rewrap, seal / unseal, CLI.
Troubleshooting
Cause: vault boot failed — N missing secret(s): with every gap listed. On a TTY, oke dev
prompts each into .env.local; otherwise use oke vault set / env / managed, or a dev:
fallback. vault.env.required joins the same list.
fx.vault.get ran after boot without that name in the merged bag. Declare the contract, ensure a
resolution layer supplies it, and import the declaring module before oke().
Mutations require drivers.vault = "vault" (encrypted-at-rest adapter). get works on env /
memory / managed bags; writes need the builtin backend.
Prefer holding Redacted until .reveal() at the edge. Revealed cleartext is scrubbed from
fx.log when the value is registered — but third-party loggers bypass Vault redaction.
Export OKE_VAULT_MASTER_KEY or run oke vault unseal. See Key
Rotation.
Learn more
- Secrets — options, Redacted, effects, tenancy paths
- Config — cleartext contracts and
vault.env - Key Rotation —
oke vault rotate/rotate-master - fx —
fx.vault.get·set·rotate·delete·list·status - Errors —
VaultBootError·VaultError·VaultSealed - Environment variables —
OKE_VAULT_*