> ## Documentation Index
> Fetch the complete documentation index at: https://docs.userefract.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Local Vault

> Development and test data-key wrap. No AWS SDK.

**Local Vault** wraps each data key with `VAULT_LOCAL_MASTER_KEY` (base64, 32 bytes). It does not call AWS. The master key is required. There is no fallback key.

<details>
  <summary>🤖 Agent context</summary>

  Package: `tooling-vault-local` at `apps/tools/vault/local/`. Contract: `VaultKeyProvider` in `apps/shared/src/vault/types.ts`. Loader: `apps/backend/src/tools/vault/loader.ts` dynamic-imports this package only when `provider` is `local`.

  ❌ Never set `local` in production or staging. Boot throws.
  ❌ Never import `@aws-sdk/client-kms` from this package.
  ❌ Never skip `VAULT_KMS_CAPABILITY`. Encrypt processes cannot unwrap.

  Verify with: `make test module=tooling-vault-local`
</details>

## Why

Local development and Jest need envelope encryption without a CMK. The envelope JSON matches AWS KMS so a later provider switch only re-wraps the data key.

## Setup

1. `tooling-vault-local` is a workspace dependency of `apps/backend`.

2. `apps/backend/src/configuration/development.ts` calls `resolveDevelopmentVault()`. That returns no vault unless `VAULT_PROVIDER=local`.

3. Environment:

```bash theme={null}
VAULT_PROVIDER=local
VAULT_LOCAL_MASTER_KEY=MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=
VAULT_KMS_CAPABILITY=encrypt
```

Compose sets `VAULT_PROVIDER=local` on `backend` and `consumer`, with `VAULT_KMS_CAPABILITY=encrypt` on the webapp and `decrypt` on the consumer.

4. `apps/backend/src/configuration/test.ts` omits vault so existing suites boot. Tests that enable vault pass a config object.

## Local observability

Domain logs use feature logger `vault`. Provider errors are `VaultError` codes. The local package does not log plaintext.

## Adding a new provider

Add a package under `apps/tools/vault/<name>/`, a `VaultProvider` enum value, a Zod branch, and a dynamic import in the loader. Do not import that package from `apps/backend` except through the loader variable.

## Gotchas

* Capability is checked before wrap and unwrap. An encrypt-only process fails `use()` with `capability_mismatch` and does not unwrap.
* A blob whose encryption context does not match fails closed (`decrypt_failed`).
* Production smoke leaves `VAULT_PROVIDER` unset and must still boot.

## What's next

AWS KMS for deployed environments: [AWS KMS](https://docs.userefract.io/tooling/vault/awsKms). Design and the incident runbook: [Vault](https://docs.userefract.io/architecture/vault.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.