HashiCorp Vault
Use HashiCorp Vault for MainSDK secrets, encryption keys and crypto - KV and Transit setup, auth methods, policies, main.yaml, testing and swapping.
HashiCorp Vault is the default driver behind secrets, and its Transit engine can sit behind keys and crypto. Your app reads provider keys from Vault's KV secrets engine on the server, and with Transit, wrapping keys and signing keys stay inside Vault and never reach your code. Pick Vault when your team already runs it, self-hosted or managed, or when you want one place for secrets and keys that is not tied to a single cloud.
Interfaces it serves
Pick HashiCorp Vault in main.yaml for any of these. Your calls stay the same, and you can swap later.
secretsBuilding for 0.2Driver vault HashiCorp VaultDefault
secrets:
driver: vaultkeysBuilding for 0.3Driver vault-transit Vault Transit
keys:
driver: vault-transitSwap to another provider
cryptoBuilding for 0.3Driver vault-transit Vault Transit
planned; use the keys interface today
crypto:
driver: vault-transitSwap to another provider
Before you start
You need a Vault server your app can reach over the network, either one you run or a managed Vault cluster.
- Enable a KV secrets engine for your app's secrets, and write each key under the name you will use as
secret:NAME, such asRESEND_API_KEY. How asecret:name maps to a KV mount and path is not defined yet. - Enable the Transit engine if you use Vault for
keysorcrypto, and create one encryption key per MainSDK region (US, EU and India) forkeys. - Set up an auth method for your app. Prefer a workload identity method over static tokens: the Kubernetes auth method when the app runs in Kubernetes, or the JWT/OIDC auth method when your platform issues identity tokens to workloads. Bind it to a role for the app.
- Write a policy that gives that role only what it needs: read on the KV paths that hold its secrets, and use of its Transit keys (encrypt, decrypt and data key operations) for
keysandcrypto. Attach the policy to the role.
Keep root and admin tokens out of the app entirely. The app should only ever hold the identity your auth method gives it.
Configure
Secrets read from Vault, and other blocks refer to them by name:
secrets:
driver: vault
vault:
address: https://vault.pinecart.internal # your Vault server
auth: workload-identity # prefer workload identity over static tokens
email:
key: secret:RESEND_API_KEY # suggested name; resolved from Vault at startupWrapping keys in Transit, one per region:
keys:
driver: vault-transit
key: # [to be decided] the identifier form the driver takes for a Transit key
us: pinecart-us
eu: pinecart-eu
in: pinecart-in
per-user: true
rotate: 365dField encryption and signing through Transit:
crypto:
driver: vault-transitKMS credentials for keys come from workload identity or the secrets driver, never from code. How the vault-transit driver gets its Vault address and auth, and which keys crypto uses, are not specified yet.
Use it
Read a credential your own code needs:
import { secrets } from "mainsdk"; // server only: a client import fails the build
export async function partnerHeaders() {
const token = await secrets.get("PARTNER_API_TOKEN");
return { authorization: `Bearer ${token}` };
}Seal a value with a data key wrapped by the owner's region key in Transit:
import { keys } from "mainsdk";
export async function sealNote(note: string, user: { id: string }) {
return keys.encrypt(note, { owner: user, region: "eu", label: "phi" });
}Local development and testing
- No Vault server: use the
localsecrets driver (.mainsdk/secrets.json, git-ignored) and thelocalkeys driver in amain.env.dev.yamloverlay.mainsdk checkrefuses the local keys driver in production withMAIN-KEYS-0001. - A real Vault on your machine: Vault's dev server mode starts an in-memory, unsealed server for trying the KV and Transit drivers. Never use dev mode for anything you keep. MainSDK's own keys conformance suite runs the Transit driver against a Vault dev server.
- Tests:
createTestApp()from@mainsdk/testingruns every interface on its in-memory fake, andchaos.on("secrets", { outage: true })simulates Vault being unreachable.
import { it } from "node:test";
import assert from "node:assert/strict";
import { createTestApp, chaos } from "@mainsdk/testing";
import { secrets } from "mainsdk";
it("config-secrets.AC4 serves the last good value when Vault is down", async () => {
createTestApp({ main: "./main.yaml" });
const first = await secrets.get("PARTNER_API_TOKEN");
chaos.on("secrets", { outage: true });
assert.equal(await secrets.get("PARTNER_API_TOKEN"), first);
});What MainSDK adds on top
secretsis importable only in server code, and values are redacted from logs, errors and telemetry.- If Vault becomes unreachable after a value was read, the last good value is served from memory and
MAIN-SECRET-0007is logged; nothing is written to disk. - A key in code, a committed env file or the browser bundle fails
mainsdk check. - With Transit behind
keys: a data key per record, one wrapping key per region, every key use audited without key material, and automatic rotation with old data still readable.
See Secrets, Encryption keys and Cryptography for the full contracts.
Limits and notes
- The
vaultsecrets driver comes with the config-secrets spec (phase 1), and thevault-transitkeys driver with the keys spec (phase 1). - Your Vault cluster's location decides where secrets and wrapping keys live. For
keys, run or choose a Transit key per region so each record stays wrapped by its owner's region key. - Rotated KV values are picked up on the next
secrets.get()after the driver's cache TTL, with no restart. The TTL length is not decided yet. secrets.raw()andkeys.raw()return the provider client for Vault features the interfaces do not wrap. Each use is logged andmainsdk checklists it.
Swap to or from it
secrets:
driver: aws-secrets-manager # was vaultFAQ
Do I need a Vault token in my app? Not if you use a workload identity auth method such as Kubernetes or JWT/OIDC, which MainSDK prefers. Static tokens are the fallback, not the default.
Can I use Vault for secrets and AWS KMS for keys? Yes. Each interface has its own driver; set secrets.driver: vault and keys.driver: aws-kms.
Does MainSDK cache secrets on disk if Vault goes down? No. The last good value lives in memory only.
Next
Open questions
Was this page helpful?