MainSDK
Search⌘K
Integrations

HashiCorp Vault

Use HashiCorp Vault for MainSDK secrets, encryption keys and crypto - KV and Transit setup, auth methods, policies, main.yaml, testing and swapping.

3 interfaces3 driversWritten guide
Updated 2026-10-28

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.

Before you start

You need a Vault server your app can reach over the network, either one you run or a managed Vault cluster.

  1. Enable a KV secrets engine for your app's secrets, and write each key under the name you will use as secret:NAME, such as RESEND_API_KEY. How a secret: name maps to a KV mount and path is not defined yet.
  2. Enable the Transit engine if you use Vault for keys or crypto, and create one encryption key per MainSDK region (US, EU and India) for keys.
  3. 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.
  4. 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 keys and crypto. 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:

main.yaml
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 startup

Wrapping keys in Transit, one per region:

main.yaml
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: 365d

Field encryption and signing through Transit:

main.yaml
crypto:
  driver: vault-transit

KMS 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:

app/lib/partner.ts
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:

app/lib/notes.ts
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 local secrets driver (.mainsdk/secrets.json, git-ignored) and the local keys driver in a main.env.dev.yaml overlay. mainsdk check refuses the local keys driver in production with MAIN-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/testing runs every interface on its in-memory fake, and chaos.on("secrets", { outage: true }) simulates Vault being unreachable.
app/lib/partner.test.ts
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

  • secrets is 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-0007 is 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 vault secrets driver comes with the config-secrets spec (phase 1), and the vault-transit keys 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() and keys.raw() return the provider client for Vault features the interfaces do not wrap. Each use is logged and mainsdk check lists it.

Swap to or from it

main.yaml
secrets:
  driver: aws-secrets-manager   # was vault

FAQ

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?

↑↓ moveEnter openEsc close

Not decided yet