workflow
Version:
Workflow SDK - Build durable, resilient, and observable workflows
131 lines (92 loc) • 6.24 kB
text/mdx
---
title: Encryption
description: Learn how Workflow SDK encrypts user data end-to-end in the event log.
type: conceptual
summary: Understand how workflow and step data is encrypted at rest.
prerequisites:
- /docs/how-it-works/event-sourcing
related:
- /docs/observability
- /docs/deploying/world/vercel-world
---
<Callout>
This guide explains how Workflow SDK encrypts user data in the event log. Understanding these details is not required to use workflows — encryption is automatic and requires no code changes. For getting started, see the [getting started](/docs/getting-started) guides for your framework.
</Callout>
Workflow SDK supports automatic end-to-end encryption of all user data before it is written to the event log. When a `World` implementation provides encryption support, it is safe to pass sensitive data — such as API keys, tokens, or user credentials — as workflow inputs, step arguments, and return values. The storage backend only ever sees ciphertext.
Encryption support varies by `World` implementation. See the [Worlds](/worlds) page to check which worlds support this feature. `World` implementations opt into encryption by providing a `getEncryptionKeyForRun()` method — the core runtime will use it automatically when present.
## What Is Encrypted
All user data flowing through the event log is encrypted:
- **Workflow inputs** — arguments passed when starting a workflow
- **Workflow return values** — the final output of a workflow
- **Step inputs** — arguments passed to step functions
- **Step return values** — the result returned by step functions
- **Hook metadata** — data attached when creating a hook
- **Hook payloads** — data received by hooks and webhooks
- **Stream data** — each frame in a `ReadableStream` or `WritableStream`
Metadata such as workflow names, step names, entity IDs, timestamps, and lifecycle states are **not** encrypted. This allows the observability tools to display run structure and timelines without requiring decryption.
## How It Works
### Key Management
Each workflow run is encrypted with its own unique key, provided by the `World` implementation via `getEncryptionKeyForRun()`. How the key is generated and stored is up to the `World`.
For example, the [Vercel World](/docs/deploying/world/vercel-world) provides unique keys per run and execution environment, ensuring that a given run can only decrypt data from that run itself.
### Encryption Algorithm
Data is encrypted using **AES-256-GCM** via the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API):
- A random 12-byte nonce is generated for each encryption operation
- The GCM authentication tag provides integrity verification — any tampering with the ciphertext is detected
- The same plaintext produces different ciphertext each time due to the random nonce
## Decrypting Data
When viewing workflow runs through the observability tools, encrypted fields display as locked placeholders until you explicitly choose to decrypt them.
### Permissions
Decryption access is controlled by the `World` implementation. On Vercel, decryption follows the same permissions model as project environment variables — if you don't have permission to view environment variable values for a project, you won't be able to decrypt workflow data either. Each decryption request is recorded in your [Vercel audit log](https://vercel.com/docs/audit-log), giving your team full visibility into when and by whom workflow data was accessed.
### Web Dashboard
Click the **Decrypt** button in the run detail panel to decrypt all data fields. Decryption happens entirely in the browser via the Web Crypto API — the observability server retrieves the encryption key but never sees your plaintext data.
### CLI
Add the `--decrypt` flag to any `inspect` command:
```bash
# Inspect a specific run
npx workflow inspect run <run-id> --decrypt
# Inspect a specific step
npx workflow inspect step <step-id> --run <run-id> --decrypt
# List events for a run
npx workflow inspect events --run <run-id> --decrypt
# Inspect a specific stream
npx workflow inspect stream <stream-id> --run <run-id> --decrypt
```
Without `--decrypt`, encrypted fields display as `🔒 Encrypted` placeholders.
## Custom World Implementations
The core runtime encrypts data automatically when the `World` implementation provides a `getEncryptionKeyForRun()` method. The core runtime can call this method in two forms:
{/* @skip-typecheck - interface signature, not runnable code */}
```typescript
getEncryptionKeyForRun?(run: WorkflowRun): Promise<Uint8Array | undefined>;
getEncryptionKeyForRun?(
runId: string,
context?: Record<string, unknown>
): Promise<Uint8Array | undefined>;
```
Use `getEncryptionKeyForRun(run)` when the run entity already exists. Use `getEncryptionKeyForRun(runId, context?)` in runtime paths like `start()` where the run has not been created yet but the world may still need context such as `deploymentId`.
To add encryption support to a custom `World`:
1. Implement `getEncryptionKeyForRun()` on your `World` class, handling both call shapes
2. Return the raw 32-byte key as a `Uint8Array` — the core runtime uses it for AES-256-GCM operations
3. Ensure the same key is returned for the same run ID across invocations (for decryption during replay)
```typescript
import type { WorkflowRun, World } from "@workflow/world";
export const getEncryptionKeyForRun: World["getEncryptionKeyForRun"] = async (
run: WorkflowRun | string,
context?: Record<string, unknown>
) => {
const runId = typeof run === "string" ? run : run.runId;
const deploymentId =
typeof run === "string"
? (context?.deploymentId as string | undefined)
: run.deploymentId;
return await lookupRunKey(runId, deploymentId);
};
async function lookupRunKey(
runId: string,
deploymentId?: string
): Promise<Uint8Array | undefined> {
// Look up or derive the encryption key for this run
// Return undefined to skip encryption
return new Uint8Array(32);
}
```
The [Vercel World](/docs/deploying/world/vercel-world) implementation uses HKDF derivation from a deployment-scoped key, but any consistent key management scheme will work.