---
title: "HashiCorp Vault"
description: "Use HashiCorp Vault for MainSDK secrets, encryption keys and crypto - KV and Transit setup, auth methods, policies, main.yaml, testing and swapping."
section: "integrations"
canonical: "https://mainsdk.dev/docs/integrations/hashicorp-vault"
updated: "2026-10-28"
---

# HashiCorp Vault

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

Canonical: https://mainsdk.dev/docs/integrations/hashicorp-vault · Updated: 2026-10-28

HashiCorp Vault is the default driver behind [`secrets`](https://mainsdk.dev/docs/interfaces/secrets), and its Transit engine can sit behind [`keys`](https://mainsdk.dev/docs/interfaces/keys) and [`crypto`](https://mainsdk.dev/docs/interfaces/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

### Secrets (`secrets`)

- Status: Building for 0.2 · ships as a stable API
- Driver: `vault` (HashiCorp Vault, default)
- Docs: https://mainsdk.dev/docs/interfaces/secrets.md

```yaml
secrets:
  driver: vault
```

Swap to another provider: `aws-secrets-manager`, `gcp-secret-manager`, `azure-key-vault`, `doppler`, `infisical`, `env`, `local`.

### Encryption keys (`keys`)

- Status: Building for 0.3 · ships as a beta API
- Driver: `vault-transit` (Vault Transit)
- Docs: https://mainsdk.dev/docs/interfaces/keys.md

```yaml
keys:
  driver: vault-transit
```

Swap to another provider: `aws-kms`, `gcp-kms`, `azure-keyvault`, `local`.

### Cryptography (`crypto`)

- Status: Building for 0.3 · ships as a beta API
- Driver: `vault-transit` (Vault Transit). planned; use the keys interface today
- Docs: https://mainsdk.dev/docs/interfaces/crypto.md

```yaml
crypto:
  driver: vault-transit
```

Swap to another provider: `builtin`, `cloud-kms`, `libsodium`.

## 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 [TBD].
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:

```yaml title="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:

```yaml title="main.yaml"
keys:
  driver: vault-transit
  key:                      # [TBD] 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:

```yaml title="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 [TBD].

## Use it

Read a credential your own code needs:

```ts title="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:

```ts title="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.

```ts title="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](https://mainsdk.dev/docs/interfaces/secrets), [Encryption keys](https://mainsdk.dev/docs/interfaces/keys) and [Cryptography](https://mainsdk.dev/docs/interfaces/crypto) 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.

> **Note:** `crypto` has no spec yet. The `vault-transit` crypto driver and its options are provisional, taken from the catalog and the product requirements, and may change when the spec lands.

## Swap to or from it

```yaml title="main.yaml"
secrets:
  driver: aws-secrets-manager   # was vault
```

> **Warning:** Secrets are read in place and MainSDK copies nothing out of Vault. Before you swap, make the same names available in the new manager. For `keys` and `crypto`, data sealed or signed with Transit keys still needs Transit to unwrap or verify, so plan a rewrap of existing data keys rather than a switch. A guided migration between KMS drivers is not specified yet.

## 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

- [Secrets](https://mainsdk.dev/docs/interfaces/secrets), [Encryption keys](https://mainsdk.dev/docs/interfaces/keys) and [Cryptography](https://mainsdk.dev/docs/interfaces/crypto)
- [AWS KMS](https://mainsdk.dev/docs/integrations/aws-kms), [AWS Secrets Manager](https://mainsdk.dev/docs/integrations/aws-secrets-manager) and [Infisical](https://mainsdk.dev/docs/integrations/infisical)
- [Swap a driver](https://mainsdk.dev/docs/guides/swap-a-driver)
