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

# AWS KMS Vault

> Wrap and unwrap data keys with AWS KMS. The SDK loads only when this provider is selected.

**AWS KMS Vault** calls `GenerateDataKey` or `Decrypt` for the data key only. Secret plaintext never goes to KMS. The AWS SDK is imported inside `tooling-vault-aws-kms`, and the backend loader imports that package only when `provider` is `aws_kms`.

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

  Package: `tooling-vault-aws-kms` (`@aws-sdk/client-kms`). Config: `awsKmsVaultSchema` in `apps/shared/src/vault/schema.ts`. Deployed resolver: `resolveDeployedVault` in `apps/backend/src/configuration/resolveVaultFromEnv.ts`.

  ❌ Never import `@aws-sdk/client-kms` from `apps/backend`.
  ❌ Never grant the webapp `kms:Decrypt` or the consumer `kms:GenerateDataKey`.
  ❌ Never log `Plaintext` or `CiphertextBlob`. Provider errors are remapped to `access_denied`, `decrypt_failed`, or `kms_error`.

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

## Why

Production keys should live in a CMK with automatic rotation. The app still holds only a wrapped data key plus the AES-GCM ciphertext.

## Setup

1. Add `tooling-vault-aws-kms` to `apps/backend` (already a workspace dependency).

2. Set environment on the deployed service:

```bash theme={null}
VAULT_PROVIDER=aws_kms
VAULT_KMS_CAPABILITY=encrypt
VAULT_KMS_KEY_ARN=arn:aws:kms:us-east-1:123456789012:key/00000000-0000-0000-0000-000000000000
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
```

The consumer process uses `VAULT_KMS_CAPABILITY=decrypt` and a principal that can `kms:Decrypt` only. Omit `VAULT_PROVIDER` to leave vault disabled. `local` throws at boot outside development and test.

3. Key policy: require encryption context `org_id`, split GenerateDataKey from Decrypt, rotation on. The repo does not provision the key. See [Vault](https://docs.userefract.io/architecture/vault.md).

## Local observability

KMS exceptions are stripped of `Plaintext`, `CiphertextBlob`, and secret-like strings before they leave the package. Domain metrics: `vault.operation` tags `action` and `outcome` only.

## Adding a new command

Do not add `Encrypt` or `Decrypt` of the secret itself. New KMS calls stay inside this package and must map failures to the closed `VaultError` codes.

## Gotchas

* `capability_mismatch` is raised before the SDK call. `access_denied` means KMS was called and denied.
* Encryption context on `GenerateDataKey` and `Decrypt` is `{org_id, name, secret_id}`. A moved blob fails closed.
* CloudTrail should alarm on `Decrypt` volume and `AccessDenied` for this key ARN.

## What's next

Local wrap for development: [Local Vault](https://docs.userefract.io/tooling/vault/local).


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