# Secret Manager

Convex component for encrypted secret vaults and issued API key lifecycle

This is an upstream package. It has not been adapted or qualified for Bijection.

- Package: convex-secret-manager
- Source: https://github.com/phantasy-bot/convex-secret-manager
- npm: https://www.npmjs.com/package/convex-secret-manager
- Directory: https://bijection.com/components/convex-secret-manager

Read the upstream README for setup, license, and compatibility before using it.

## Upstream package documentation

Recorded 2026-10-23. This describes convex-secret-manager; check the Bijection adaptation separately.

Secret Manager is a Convex component that combines encrypted secret vaults and issued API key lifecycle management into a single package with per-ownerId tenancy. The vault module stores third-party credentials encrypted at rest using envelope encryption with KEK rotation support, while the issued keys module handles machine token creation, validation, refresh, revocation, and audit trails. It targets multi-tenant backends where a single owner entity needs to hold both external credentials and issued machine tokens.

### Use cases

- Storing per-organization API keys (OpenAI, Venice, webhook secrets) encrypted at rest with KEK rotation support for a SaaS multi-tenant backend
- Issuing and managing machine tokens with hash-only storage, grace-period rotation, and idle/expiry sweeps for agent or service-to-service authentication
- Building a unified secrets layer for a multi-tenant app where each tenant (org, user, or deployment) needs isolated credential namespaces
- Auditing access and lifecycle events for both stored secrets and issued keys via a paginated audit trail
- Replacing two separate gaganref packages (convex-secret-store and convex-api-keys) when one owner must hold both third-party credentials and issued tokens

### How it works

Configuration starts in `convex/convex.config.ts` where you call `defineKeys` to supply versioned key-encryption-key (KEK) material via environment variables, then attach the component with `app.use(secretManager, ...)`. A `SecretManager` instance is constructed in your Convex module file by passing `components.secretManager`, giving you access to both the vault and issued keys APIs.

The vault module exposes `vault.putPlaintext` for envelope-encrypted writes with additional authenticated data (AAD), `vault.getResult` for safe reads returning a `{ ok, value }` discriminated union, `vault.update` for metadata and TTL changes without re-encryption, and `vaultRotate.rotate` with `isRotationComplete` for draining KEK rotations. Secrets are scoped by a three-part path: `ownerId`, `namespace`, and `name`.

The issued keys module provides `issued.create`, `issued.validate` (a side-effect-free query), `issued.touch`, `issued.revoke`, `issued.revokeAll`, and `issued.refresh` for grace-period token rotation. Keys use `sm_` prefixed tokens with hash-only storage. Both modules include built-in hourly cron sweeps for expired and idle records, plus `cleanupKeys`, `cleanupEvents`, and `auditEvents.listEvents` for operational maintenance and audit review.

### Upstream README

# convex-secret-manager

Convex component for **encrypted secret vaults** and **issued API key lifecycle**.

Combines the roles of [gaganref/convex-secret-store](https://www.convex.dev/components/convex-secret-store) and [gaganref/convex-api-keys](https://www.convex.dev/components/convex-api-keys) into one package with per-`ownerId` tenancy. Any Convex app can use it (SaaS orgs, agents, multi-tenant backends).

## Two modules

| Module | Use |
|--------|-----|
| **Vault** | Store user-supplied credentials (OpenAI, Venice, webhooks) encrypted at rest |
| **Issued keys** | Issue `sm_` machine tokens with hash-only storage, refresh, revoke, and audit |

## vs gaganref

| | gaganref (2 packages) | `convex-secret-manager` |
|--|----------------------|-------------------------|
| Path model | `namespace` + `name` | `ownerId` + `namespace` + `name` |
| Use case | Generic apps | Multi-tenant backends that issue keys too |
| Install | Two components | One component |
| Vault crypto | Envelope + KEK rotation | Envelope (`defineKeys`) + legacy single-key |
| Issued validate | Query (side-effect free) | Query |
| Sweeps | Hourly crons | Hourly crons (built-in) |

Use gaganref when you need only one concern. Use this package when one owner should hold both third-party credentials and issued machine tokens.

## Install

```bash
npm install convex-secret-manager
```

```ts
// convex/convex.config.ts
import { defineApp } from "convex/server";
import { v } from "convex/values";
import { defineKeys } from "convex-secret-manager";
import secretManager from "convex-secret-manager/convex.config.js";

const app = defineApp({
  env: {
    MY_APP_KEK_V1: v.string(),
  },
});

app.use(secretManager, {
  env: {
    SECRET_MANAGER_KEYS: defineKeys({
      1: process.env.MY_APP_KEK_V1!,
    }),
  },
});

export default app;
```

```ts
// convex/secrets.ts
import { SecretManager } from "convex-secret-manager";
import { components } from "./_generated/api.js";

export const secretManager = new SecretManager(components.secretManager);
```

Set on the Convex deployment:

```env
SECRET_MANAGER_KEYS=1:<kek-material>
# or legacy single key:
SECRET_MANAGER_ENCRYPTION_KEY=...
```

## Vault paths

```text
ownerId   = orgId | userId | deployment
namespace = providers | integrations | webhooks
name      = openai.apiKey
```

## Issued keys API (parity highlights)

- `issued.create` / `validate` (query) / `touch` / `revoke` / `revokeAll`
- `issued.refresh` — rotate with grace period
- `issued.update` / `getKey` / `list` (paginated + `effectiveStatus`)
- Hourly sweep crons for expired and idle keys
- `cleanupKeys` / `cleanupEvents` internal jobs

## Vault API (parity highlights)

- `vault.putPlaintext` — component-side envelope encryption with AAD
- `vault.getResult` — `{ ok, value } | { ok: false, reason }`
- `vault.update` — metadata/TTL without re-encrypt
- `vault.list` — paginated with `effectiveState`
- `auditEvents.listEvents` — paginated audit trail
- `vaultRotate.rotate` / `isRotationComplete` — KEK rotation drain
- `vaultCleanup.cleanupSecrets` — expired secret cleanup

## Example

See [`example/`](./example/) for a minimal Convex + Vite dashboard.

## License

MIT