Secret Management (Lockbox)
Relevant source files
The following files were used as context for generating this wiki page:
The Lockbox system provides secure, encrypted storage for user-provided credentials, such as API keys, within the application's UI-based workflow environment. It is designed to handle sensitive data for production-like scenarios, contrasting with local development where secrets are managed through .env files. The system ensures that secrets are encrypted at rest and can only be decrypted server-side for use during workflow execution.
The core functionality includes encrypting secrets, storing them in a dedicated Supabase database schema, versioning them upon updates, and providing a secure API for creation and retrieval. During workflow invocation, the secretResolver component is intended to access these encrypted keys, ensuring that plaintext credentials are not exposed on the client-side or in local configuration files.
Sources: README.md, apps/web/src/app/api/lockbox/secrets/route.ts
Architecture
The Lockbox system is composed of several key components: an API endpoint for managing secrets, encryption/decryption logic, and a database persistence layer within a Supabase lockbox schema.
The diagram below illustrates the high-level architecture of the Lockbox system.
Database Schema
Secrets are stored in the user_secrets table within the lockbox schema in the Supabase database. This table holds the encrypted secret, versioning information, and metadata.
The following diagram shows the entity relationship for the user_secrets table.
user_secrets Table Columns
| Column | Type | Description |
|---|---|---|
user_secret_id | uuid | Primary key for the secret record. |
clerk_id | uuid | Foreign key linking to the user in auth.users. |
name | text | The name of the secret (e.g., OPENAI_API_KEY). |
namespace | text | A namespace to group secrets, defaults to default. |
version | integer | The version number of the secret, incremented on each update. |
ciphertext | text | The encrypted secret value. |
iv | text | The initialization vector used for AES-256-GCM encryption. |
auth_tag | text | The authentication tag generated by AES-256-GCM. |
is_current | boolean | Flag indicating if this is the currently active version of the secret. |
last_used_at | timestamptz | Timestamp of when the secret was last revealed/used. |
created_at | timestamptz | Timestamp of when the record was created. |
updated_at | timestamptz | Timestamp of when the record was last updated. |
created_by | uuid | The user ID of the creator. |
updated_by | uuid | The user ID of the last updater. |
Sources: packages/shared/src/types/lockbox.types.ts:11-30, apps/web/src/app/api/lockbox/secrets/route.ts:15-17, 100-102
Encryption and Decryption
The system uses AES-256-GCM for authenticated encryption. This ensures both confidentiality and integrity of the stored secrets.
Key Encryption Key (KEK)
All encryption operations rely on a single server-side Key-Encryption Key (KEK) defined by the LOCKBOX_KEK environment variable. This key is critical for the security of the entire system and must be a 32-byte value.
| Environment Variable | Description | Validation |
|---|---|---|
LOCKBOX_KEK | 32-byte key for AES-256-GCM encryption of user secrets. It is required for the Lockbox API to function. | Must be a 32-byte key, which can be represented as 64 hex characters, 44 base64 characters, or 32 raw characters. |
Sources: packages/shared/src/env-models.ts:70-88, apps/web/src/lib/secret-management/lockbox.ts:10-12
Core Functions
encryptGCM(plaintext: string): Takes a plaintext string, encrypts it usingLOCKBOX_KEK, and returns an object containing theciphertext,iv(Initialization Vector), andauthTag.decryptGCM({ ciphertext, iv, authTag }): Takes the encrypted components, decrypts them usingLOCKBOX_KEK, and returns the original plaintext string.
These functions are located in apps/web/src/lib/secret-management/lockbox.ts.
Sources: apps/web/src/lib/secret-management/lockbox.ts:24-57
API Endpoint: /api/lockbox/secrets
This API route is the primary interface for managing secrets. It requires user authentication via Clerk.
Sources: apps/web/src/app/api/lockbox/secrets/route.ts
POST /api/lockbox/secrets
This endpoint is used to create a new secret or rotate an existing one. It always creates a new version, marks it as is_current, and demotes the previous version. It never returns the plaintext value.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | The name of the secret. |
value | string | Yes | The plaintext value of the secret to be encrypted. |
namespace | string | No | An optional namespace for the secret. Defaults to default. |
Sources: apps/web/src/app/api/lockbox/secrets/route.ts:26-34
Secret Creation and Versioning Flow
The following diagram shows the sequence of operations when a new secret is posted.
GET /api/lockbox/secrets
This endpoint retrieves the current version of a secret. It can return either metadata only or the decrypted plaintext value, controlled by the reveal query parameter.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | The name of the secret to retrieve. |
namespace | string | No | The namespace of the secret. |
reveal | string | No | If set to "1", the endpoint returns the decrypted plaintext value. Otherwise, it returns only metadata. |
Sources: apps/web/src/app/api/lockbox/secrets/route.ts:105-108
Secret Retrieval Flow
This diagram shows the process for retrieving a secret, both with and without revealing the value.
When a secret is revealed, its last_used_at timestamp is updated in the database. This update is non-critical; a failure to update the timestamp will be logged but will not prevent the decrypted secret from being returned to the user.
Sources: apps/web/src/app/api/lockbox/secrets/route.ts:148-152