Skip to content

Add end-to-end encryption for workflow user data#950

Draft
TooTallNate wants to merge 7 commits intomainfrom
e2e-encryption
Draft

Add end-to-end encryption for workflow user data#950
TooTallNate wants to merge 7 commits intomainfrom
e2e-encryption

Conversation

@TooTallNate
Copy link
Member

This implements AES-256-GCM encryption with per-run key derivation via
HKDF-SHA256 for workflow user data.

Key changes:

  • Add encryption module (packages/world-vercel/src/encryption.ts) with
    createEncryptor() and createEncryptorFromEnv() functions
  • Add Encryptor, EncryptionContext, KeyMaterial interfaces to @workflow/world
  • Make all (de)hydrate* serialization functions async and accept encryptor
  • Add maybeEncrypt/maybeDecrypt helpers with 'encr' format prefix
  • Add getEncryptStream/getDecryptStream transform streams
  • Update runWorkflow() to take world as 4th parameter
  • Update WorkflowOrchestratorContext to include runId and world
  • Integrate encryption into WorkflowServerWritableStream/ReadableStream
  • Update hydrateResourceIO and observability helpers for async + world param
  • Update all test files with mock world and async serialization wrappers
  • Add 18 encryption unit tests covering round-trip, key isolation, tampering

Format: [encr (4 bytes)][nonce (12 bytes)][ciphertext + auth tag]

Copilot AI review requested due to automatic review settings February 5, 2026 16:55
@changeset-bot
Copy link

changeset-bot bot commented Feb 5, 2026

🦋 Changeset detected

Latest commit: f95f584

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 19 packages
Name Type
@workflow/cli Patch
@workflow/core Patch
@workflow/web-shared Patch
@workflow/world-vercel Patch
@workflow/world Patch
@workflow/world-testing Patch
workflow Patch
@workflow/builders Patch
@workflow/docs-typecheck Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/world-local Patch
@workflow/world-postgres Patch
@workflow/astro Patch
@workflow/nest Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch
@workflow/nuxt Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Contributor

github-actions bot commented Feb 5, 2026

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 0.029s (-33.6% 🟢) 1.008s (~) 0.979s 10 1.00x
💻 Local Next.js (Turbopack) 0.039s (+2.7%) 1.017s (~) 0.978s 10 1.35x
💻 Local Express 0.044s (+35.8% 🔺) 1.008s (~) 0.964s 10 1.54x
🐘 Postgres Nitro 0.192s (+28.2% 🔺) 1.015s (~) 0.823s 10 6.71x
🐘 Postgres Express 0.215s (-21.3% 🟢) 1.024s (+0.8%) 0.808s 10 7.53x
🐘 Postgres Next.js (Turbopack) 0.413s (+8.2% 🔺) 1.022s (~) 0.609s 10 14.45x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 0.683s (+6.4% 🔺) 1.732s (+5.5% 🔺) 1.049s 10 1.00x
▲ Vercel Nitro 0.715s (-5.6% 🟢) 1.594s (+6.3% 🔺) 0.879s 10 1.05x
▲ Vercel Next.js (Turbopack) 0.754s (+18.1% 🔺) 1.614s (+4.9%) 0.860s 10 1.10x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.076s (-3.8%) 2.007s (~) 0.931s 10 1.00x
💻 Local Next.js (Turbopack) 1.098s (~) 2.013s (~) 0.915s 10 1.02x
💻 Local Express 1.116s (+3.5%) 2.007s (~) 0.892s 10 1.04x
🐘 Postgres Next.js (Turbopack) 1.807s (-18.2% 🟢) 2.231s (-26.2% 🟢) 0.424s 10 1.68x
🐘 Postgres Nitro 2.338s (-4.3%) 3.015s (~) 0.677s 10 2.17x
🐘 Postgres Express 2.404s (+10.0% 🔺) 3.014s (~) 0.610s 10 2.23x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.713s (-5.7% 🟢) 3.535s (-4.1%) 0.822s 10 1.00x
▲ Vercel Express 2.717s (-5.0% 🟢) 3.665s (-3.3%) 0.948s 10 1.00x
▲ Vercel Next.js (Turbopack) 2.979s (+0.6%) 3.776s (+2.6%) 0.797s 10 1.10x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 10.543s (-3.2%) 11.013s (~) 0.470s 3 1.00x
💻 Local Next.js (Turbopack) 10.720s (~) 11.018s (~) 0.299s 3 1.02x
💻 Local Express 10.835s (+2.8%) 11.016s (~) 0.180s 3 1.03x
🐘 Postgres Next.js (Turbopack) 15.437s (-24.0% 🟢) 16.032s (-23.8% 🟢) 0.595s 2 1.46x
🐘 Postgres Express 20.410s (~) 21.034s (~) 0.624s 2 1.94x
🐘 Postgres Nitro 20.451s (~) 21.039s (~) 0.588s 2 1.94x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 21.580s (-7.8% 🟢) 22.095s (-10.9% 🟢) 0.515s 2 1.00x
▲ Vercel Nitro 22.276s (-2.6%) 23.400s (-2.0%) 1.124s 2 1.03x
▲ Vercel Next.js (Turbopack) 23.073s (+1.2%) 24.038s (+1.4%) 0.965s 2 1.07x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 26.737s (-3.3%) 27.029s (-3.5%) 0.292s 3 1.00x
💻 Local Next.js (Turbopack) 27.188s (~) 28.029s (~) 0.841s 3 1.02x
💻 Local Express 27.474s (+2.6%) 28.026s (+3.7%) 0.552s 3 1.03x
🐘 Postgres Next.js (Turbopack) 37.556s (-25.6% 🟢) 38.074s (-25.5% 🟢) 0.518s 2 1.40x
🐘 Postgres Express 50.300s (~) 51.090s (~) 0.790s 2 1.88x
🐘 Postgres Nitro 50.380s (~) 51.052s (~) 0.672s 2 1.88x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 55.879s (-5.3% 🟢) 56.635s (-5.7% 🟢) 0.756s 2 1.00x
▲ Vercel Nitro 56.087s (-8.8% 🟢) 56.921s (-9.2% 🟢) 0.834s 2 1.00x
▲ Vercel Next.js (Turbopack) 57.272s (-6.7% 🟢) 58.153s (-6.8% 🟢) 0.882s 2 1.02x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 55.542s (-3.5%) 56.027s (-3.5%) 0.484s 2 1.00x
💻 Local Next.js (Turbopack) 56.659s (~) 57.042s (~) 0.383s 2 1.02x
💻 Local Express 57.077s (+2.4%) 57.563s (+2.7%) 0.486s 2 1.03x
🐘 Postgres Next.js (Turbopack) 73.825s (-26.4% 🟢) 74.133s (-26.7% 🟢) 0.308s 2 1.33x
🐘 Postgres Nitro 100.346s (~) 101.148s (~) 0.802s 1 1.81x
🐘 Postgres Express 100.565s (~) 101.086s (+0.9%) 0.521s 1 1.81x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 118.368s (-3.2%) 118.845s (-3.6%) 0.477s 1 1.00x
▲ Vercel Express 119.023s (-4.1%) 120.008s (-4.0%) 0.985s 1 1.01x
▲ Vercel Next.js (Turbopack) 120.948s (-2.5%) 121.732s (-2.2%) 0.784s 1 1.02x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.365s (-3.1%) 2.006s (~) 0.641s 15 1.00x
💻 Local Next.js (Turbopack) 1.393s (~) 2.012s (~) 0.618s 15 1.02x
💻 Local Express 1.410s (+3.4%) 2.007s (~) 0.596s 15 1.03x
🐘 Postgres Next.js (Turbopack) 2.125s (-5.6% 🟢) 2.746s (-9.0% 🟢) 0.621s 11 1.56x
🐘 Postgres Express 2.240s (~) 3.013s (~) 0.773s 10 1.64x
🐘 Postgres Nitro 2.492s (+10.6% 🔺) 3.013s (~) 0.520s 10 1.83x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 3.131s (-17.4% 🟢) 4.218s (-8.6% 🟢) 1.087s 8 1.00x
▲ Vercel Nitro 3.393s (+15.4% 🔺) 4.340s (+13.2% 🔺) 0.948s 7 1.08x
▲ Vercel Next.js (Turbopack) 3.493s (+4.3%) 4.424s (+6.9% 🔺) 0.931s 7 1.12x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 2.256s (-13.5% 🟢) 3.234s (+7.4% 🔺) 0.978s 10 1.00x
💻 Local Next.js (Turbopack) 2.455s (-2.4%) 3.038s (+0.7%) 0.583s 10 1.09x
💻 Local Express 2.545s (+13.4% 🔺) 3.013s (-6.7% 🟢) 0.467s 10 1.13x
🐘 Postgres Nitro 8.457s (+4.0%) 8.799s (~) 0.342s 4 3.75x
🐘 Postgres Express 8.915s (-7.5% 🟢) 9.323s (-7.4% 🟢) 0.408s 4 3.95x
🐘 Postgres Next.js (Turbopack) 13.105s (+12.8% 🔺) 13.401s (+11.4% 🔺) 0.296s 3 5.81x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.225s (-17.6% 🟢) 4.000s (-13.3% 🟢) 0.774s 8 1.00x
▲ Vercel Express 3.567s (+4.0%) 4.304s (~) 0.737s 7 1.11x
▲ Vercel Next.js (Turbopack) 3.625s (-1.6%) 4.454s (+2.1%) 0.829s 7 1.12x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 6.223s (-22.0% 🟢) 6.883s (-20.8% 🟢) 0.660s 5 1.00x
💻 Local Next.js (Turbopack) 6.782s (+4.8%) 8.016s (+11.1% 🔺) 1.233s 4 1.09x
💻 Local Express 7.356s (+23.5% 🔺) 8.083s (+22.0% 🔺) 0.727s 4 1.18x
🐘 Postgres Nitro 45.798s (-0.6%) 46.223s (-1.9%) 0.425s 1 7.36x
🐘 Postgres Express 52.093s (+5.7% 🔺) 52.299s (+4.2%) 0.206s 1 8.37x
🐘 Postgres Next.js (Turbopack) 54.162s (-1.7%) 54.294s (-1.7%) 0.132s 1 8.70x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 3.851s (+5.2% 🔺) 5.202s (+19.1% 🔺) 1.351s 6 1.00x
▲ Vercel Nitro 3.875s (+9.0% 🔺) 4.417s (+2.3%) 0.542s 7 1.01x
▲ Vercel Next.js (Turbopack) 4.202s (+22.8% 🔺) 5.079s (+25.1% 🔺) 0.876s 6 1.09x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.385s (-4.1%) 2.006s (~) 0.621s 15 1.00x
💻 Local Next.js (Turbopack) 1.417s (-0.9%) 2.010s (~) 0.593s 15 1.02x
💻 Local Express 1.437s (+3.8%) 2.007s (~) 0.570s 15 1.04x
🐘 Postgres Nitro 2.085s (-3.3%) 2.473s (-4.7%) 0.388s 13 1.51x
🐘 Postgres Express 2.166s (+3.2%) 2.683s (+2.6%) 0.518s 12 1.56x
🐘 Postgres Next.js (Turbopack) 2.276s (-2.3%) 2.937s (~) 0.660s 11 1.64x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.806s (-11.0% 🟢) 3.763s (-5.4% 🟢) 0.957s 8 1.00x
▲ Vercel Express 2.869s (-70.0% 🟢) 3.800s (-64.0% 🟢) 0.930s 8 1.02x
▲ Vercel Next.js (Turbopack) 2.955s (-11.2% 🟢) 3.785s (-8.0% 🟢) 0.830s 8 1.05x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 2.389s (-10.9% 🟢) 3.301s (+9.2% 🔺) 0.912s 10 1.00x
💻 Local Express 2.662s (+10.4% 🔺) 3.010s (-9.5% 🟢) 0.348s 10 1.11x
💻 Local Next.js (Turbopack) 2.769s (+7.1% 🔺) 3.116s (+3.2%) 0.346s 10 1.16x
🐘 Postgres Express 10.166s (-18.6% 🟢) 10.710s (-17.8% 🟢) 0.544s 3 4.26x
🐘 Postgres Nitro 10.658s (+6.1% 🔺) 11.038s (+6.5% 🔺) 0.380s 3 4.46x
🐘 Postgres Next.js (Turbopack) 14.383s (-2.0%) 15.059s (~) 0.675s 2 6.02x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.892s (-42.2% 🟢) 3.825s (-33.8% 🟢) 0.933s 8 1.00x
▲ Vercel Next.js (Turbopack) 2.904s (-40.7% 🟢) 3.727s (-33.1% 🟢) 0.823s 9 1.00x
▲ Vercel Nitro 2.981s (-79.8% 🟢) 3.690s (-76.4% 🟢) 0.708s 9 1.03x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 6.686s (-17.1% 🟢) 7.588s (-12.2% 🟢) 0.901s 4 1.00x
💻 Local Express 7.676s (+15.6% 🔺) 8.622s (+13.7% 🔺) 0.945s 4 1.15x
💻 Local Next.js (Turbopack) 8.358s (+13.0% 🔺) 9.480s (+17.1% 🔺) 1.121s 4 1.25x
🐘 Postgres Express 50.265s (-6.3% 🟢) 51.137s (-5.6% 🟢) 0.872s 1 7.52x
🐘 Postgres Nitro 53.655s (+0.6%) 54.200s (~) 0.545s 1 8.02x
🐘 Postgres Next.js (Turbopack) 57.590s (+5.5% 🔺) 58.248s (+5.5% 🔺) 0.658s 1 8.61x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 3.515s (-88.9% 🟢) 4.155s (-87.4% 🟢) 0.640s 8 1.00x
▲ Vercel Nitro 3.740s (-26.3% 🟢) 4.286s (-24.3% 🟢) 0.546s 7 1.06x
▲ Vercel Next.js (Turbopack) 3.745s (-77.6% 🟢) 4.545s (-73.9% 🟢) 0.800s 7 1.07x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 0.116s (-44.1% 🟢) 0.999s (+0.8%) 0.011s (-31.1% 🟢) 1.018s (-0.6%) 0.903s 10 1.00x
💻 Local Next.js (Turbopack) 0.141s (~) 1.003s (~) 0.016s (-11.8% 🟢) 1.026s (~) 0.885s 10 1.22x
💻 Local Express 0.182s (+66.0% 🔺) 0.992s (-0.7%) 0.014s (+19.1% 🔺) 1.020s (~) 0.839s 10 1.57x
🐘 Postgres Next.js (Turbopack) 0.779s (-67.5% 🟢) 0.825s (-68.9% 🟢) 0.000s (+Infinity% 🔺) 1.016s (-66.4% 🟢) 0.237s 10 6.73x
🐘 Postgres Nitro 2.296s (-0.6%) 2.746s (~) 0.000s (NaN%) 3.016s (~) 0.719s 10 19.85x
🐘 Postgres Express 2.334s (~) 2.708s (~) 0.000s (-50.0% 🟢) 3.016s (~) 0.681s 10 20.18x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 3.071s (~) 3.197s (-1.7%) 0.205s (-14.9% 🟢) 3.896s (-2.6%) 0.825s 10 1.00x
▲ Vercel Express 3.071s (+1.8%) 3.253s (-2.5%) 0.287s (+57.8% 🔺) 4.187s (+4.1%) 1.116s 10 1.00x
▲ Vercel Nitro 3.153s (+2.6%) 3.346s (+0.6%) 0.148s (-21.2% 🟢) 4.044s (+2.0%) 0.891s 10 1.03x

🔍 Observability: Next.js (Turbopack) | Express | Nitro

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Nitro 12/12
🐘 Postgres Next.js (Turbopack) 6/12
▲ Vercel Express 7/12
Fastest World by Framework

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 💻 Local 10/12
Next.js (Turbopack) 💻 Local 10/12
Nitro 💻 Local 10/12
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Starter: Community world (local development)
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run

@github-actions
Copy link
Contributor

github-actions bot commented Feb 5, 2026

🧪 E2E Test Results

Some tests failed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 479 0 38 517
✅ 💻 Local Development 438 0 32 470
✅ 📦 Local Production 438 0 32 470
✅ 🐘 Local Postgres 438 0 32 470
✅ 🪟 Windows 47 0 0 47
❌ 🌍 Community Worlds 31 169 0 200
✅ 📋 Other 129 0 12 141
Total 2000 169 146 2315

❌ Failed Tests

🌍 Community Worlds (169 failed)

mongodb (42 failed):

  • addTenWorkflow
  • addTenWorkflow
  • should work with react rendering in step
  • promiseAllWorkflow
  • promiseRaceWorkflow
  • promiseAnyWorkflow
  • readableStreamWorkflow
  • hookWorkflow
  • webhookWorkflow
  • sleepingWorkflow
  • nullByteWorkflow
  • workflowAndStepMetadataWorkflow
  • outputStreamWorkflow
  • outputStreamInsideStepWorkflow - getWritable() called inside step functions
  • fetchWorkflow
  • promiseRaceStressTestWorkflow
  • error handling error propagation workflow errors nested function calls preserve message and stack trace
  • error handling error propagation workflow errors cross-file imports preserve message and stack trace
  • error handling error propagation step errors basic step error preserves message and stack trace
  • error handling error propagation step errors cross-file step error preserves message and function names in stack
  • error handling retry behavior regular Error retries until success
  • error handling retry behavior FatalError fails immediately without retries
  • error handling retry behavior RetryableError respects custom retryAfter delay
  • error handling retry behavior maxRetries=0 disables retries
  • error handling catchability FatalError can be caught and detected with FatalError.is()
  • hookCleanupTestWorkflow - hook token reuse after workflow completion
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars)
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument
  • closureVariableWorkflow - nested step functions with closure variables
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly
  • Calculator.calculate - static workflow method using static step methods from another class
  • AllInOneService.processNumber - static workflow method using sibling static step methods
  • ChainableService.processWithThis - static step methods using this to reference the class
  • thisSerializationWorkflow - step function invoked with .call() and .apply()
  • customSerializationWorkflow - custom class serialization with WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE
  • instanceMethodStepWorkflow - instance methods with "use step" directive
  • crossContextSerdeWorkflow - classes defined in step code are deserializable in workflow context
  • pages router addTenWorkflow via pages router
  • pages router promiseAllWorkflow via pages router
  • pages router sleepingWorkflow via pages router

redis (42 failed):

  • addTenWorkflow
  • addTenWorkflow
  • should work with react rendering in step
  • promiseAllWorkflow
  • promiseRaceWorkflow
  • promiseAnyWorkflow
  • readableStreamWorkflow
  • hookWorkflow
  • webhookWorkflow
  • sleepingWorkflow
  • nullByteWorkflow
  • workflowAndStepMetadataWorkflow
  • outputStreamWorkflow
  • outputStreamInsideStepWorkflow - getWritable() called inside step functions
  • fetchWorkflow
  • promiseRaceStressTestWorkflow
  • error handling error propagation workflow errors nested function calls preserve message and stack trace
  • error handling error propagation workflow errors cross-file imports preserve message and stack trace
  • error handling error propagation step errors basic step error preserves message and stack trace
  • error handling error propagation step errors cross-file step error preserves message and function names in stack
  • error handling retry behavior regular Error retries until success
  • error handling retry behavior FatalError fails immediately without retries
  • error handling retry behavior RetryableError respects custom retryAfter delay
  • error handling retry behavior maxRetries=0 disables retries
  • error handling catchability FatalError can be caught and detected with FatalError.is()
  • hookCleanupTestWorkflow - hook token reuse after workflow completion
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars)
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument
  • closureVariableWorkflow - nested step functions with closure variables
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly
  • Calculator.calculate - static workflow method using static step methods from another class
  • AllInOneService.processNumber - static workflow method using sibling static step methods
  • ChainableService.processWithThis - static step methods using this to reference the class
  • thisSerializationWorkflow - step function invoked with .call() and .apply()
  • customSerializationWorkflow - custom class serialization with WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE
  • instanceMethodStepWorkflow - instance methods with "use step" directive
  • crossContextSerdeWorkflow - classes defined in step code are deserializable in workflow context
  • pages router addTenWorkflow via pages router
  • pages router promiseAllWorkflow via pages router
  • pages router sleepingWorkflow via pages router

starter (43 failed):

  • addTenWorkflow
  • addTenWorkflow
  • should work with react rendering in step
  • promiseAllWorkflow
  • promiseRaceWorkflow
  • promiseAnyWorkflow
  • readableStreamWorkflow
  • hookWorkflow
  • webhookWorkflow
  • sleepingWorkflow
  • nullByteWorkflow
  • workflowAndStepMetadataWorkflow
  • outputStreamWorkflow
  • outputStreamInsideStepWorkflow - getWritable() called inside step functions
  • fetchWorkflow
  • promiseRaceStressTestWorkflow
  • error handling error propagation workflow errors nested function calls preserve message and stack trace
  • error handling error propagation workflow errors cross-file imports preserve message and stack trace
  • error handling error propagation step errors basic step error preserves message and stack trace
  • error handling error propagation step errors cross-file step error preserves message and function names in stack
  • error handling retry behavior regular Error retries until success
  • error handling retry behavior FatalError fails immediately without retries
  • error handling retry behavior RetryableError respects custom retryAfter delay
  • error handling retry behavior maxRetries=0 disables retries
  • error handling catchability FatalError can be caught and detected with FatalError.is()
  • hookCleanupTestWorkflow - hook token reuse after workflow completion
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars)
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument
  • closureVariableWorkflow - nested step functions with closure variables
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step
  • health check (CLI) - workflow health command reports healthy endpoints
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly
  • Calculator.calculate - static workflow method using static step methods from another class
  • AllInOneService.processNumber - static workflow method using sibling static step methods
  • ChainableService.processWithThis - static step methods using this to reference the class
  • thisSerializationWorkflow - step function invoked with .call() and .apply()
  • customSerializationWorkflow - custom class serialization with WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE
  • instanceMethodStepWorkflow - instance methods with "use step" directive
  • crossContextSerdeWorkflow - classes defined in step code are deserializable in workflow context
  • pages router addTenWorkflow via pages router
  • pages router promiseAllWorkflow via pages router
  • pages router sleepingWorkflow via pages router

turso (42 failed):

  • addTenWorkflow
  • addTenWorkflow
  • should work with react rendering in step
  • promiseAllWorkflow
  • promiseRaceWorkflow
  • promiseAnyWorkflow
  • readableStreamWorkflow
  • hookWorkflow
  • webhookWorkflow
  • sleepingWorkflow
  • nullByteWorkflow
  • workflowAndStepMetadataWorkflow
  • outputStreamWorkflow
  • outputStreamInsideStepWorkflow - getWritable() called inside step functions
  • fetchWorkflow
  • promiseRaceStressTestWorkflow
  • error handling error propagation workflow errors nested function calls preserve message and stack trace
  • error handling error propagation workflow errors cross-file imports preserve message and stack trace
  • error handling error propagation step errors basic step error preserves message and stack trace
  • error handling error propagation step errors cross-file step error preserves message and function names in stack
  • error handling retry behavior regular Error retries until success
  • error handling retry behavior FatalError fails immediately without retries
  • error handling retry behavior RetryableError respects custom retryAfter delay
  • error handling retry behavior maxRetries=0 disables retries
  • error handling catchability FatalError can be caught and detected with FatalError.is()
  • hookCleanupTestWorkflow - hook token reuse after workflow completion
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars)
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument
  • closureVariableWorkflow - nested step functions with closure variables
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly
  • Calculator.calculate - static workflow method using static step methods from another class
  • AllInOneService.processNumber - static workflow method using sibling static step methods
  • ChainableService.processWithThis - static step methods using this to reference the class
  • thisSerializationWorkflow - step function invoked with .call() and .apply()
  • customSerializationWorkflow - custom class serialization with WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE
  • instanceMethodStepWorkflow - instance methods with "use step" directive
  • crossContextSerdeWorkflow - classes defined in step code are deserializable in workflow context
  • pages router addTenWorkflow via pages router
  • pages router promiseAllWorkflow via pages router
  • pages router sleepingWorkflow via pages router

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 43 0 4
✅ example 43 0 4
✅ express 43 0 4
✅ fastify 43 0 4
✅ hono 43 0 4
✅ nextjs-turbopack 46 0 1
✅ nextjs-webpack 46 0 1
✅ nitro 43 0 4
✅ nuxt 43 0 4
✅ sveltekit 43 0 4
✅ vite 43 0 4
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 43 0 4
✅ express-stable 43 0 4
✅ fastify-stable 43 0 4
✅ hono-stable 43 0 4
✅ nextjs-turbopack-stable 47 0 0
✅ nextjs-webpack-stable 47 0 0
✅ nitro-stable 43 0 4
✅ nuxt-stable 43 0 4
✅ sveltekit-stable 43 0 4
✅ vite-stable 43 0 4
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 43 0 4
✅ express-stable 43 0 4
✅ fastify-stable 43 0 4
✅ hono-stable 43 0 4
✅ nextjs-turbopack-stable 47 0 0
✅ nextjs-webpack-stable 47 0 0
✅ nitro-stable 43 0 4
✅ nuxt-stable 43 0 4
✅ sveltekit-stable 43 0 4
✅ vite-stable 43 0 4
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 43 0 4
✅ express-stable 43 0 4
✅ fastify-stable 43 0 4
✅ hono-stable 43 0 4
✅ nextjs-turbopack-stable 47 0 0
✅ nextjs-webpack-stable 47 0 0
✅ nitro-stable 43 0 4
✅ nuxt-stable 43 0 4
✅ sveltekit-stable 43 0 4
✅ vite-stable 43 0 4
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 47 0 0
❌ 🌍 Community Worlds
App Passed Failed Skipped
✅ mongodb-dev 3 0 0
❌ mongodb 5 42 0
✅ redis-dev 3 0 0
❌ redis 5 42 0
✅ starter-dev 3 0 0
❌ starter 4 43 0
✅ turso-dev 3 0 0
❌ turso 5 42 0
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 43 0 4
✅ e2e-local-postgres-nest-stable 43 0 4
✅ e2e-local-prod-nest-stable 43 0 4

📋 View full workflow run

@vercel
Copy link
Contributor

vercel bot commented Feb 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment Feb 5, 2026 8:34pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment Feb 5, 2026 8:34pm
example-workflow Ready Ready Preview, Comment Feb 5, 2026 8:34pm
workbench-astro-workflow Ready Ready Preview, Comment Feb 5, 2026 8:34pm
workbench-express-workflow Ready Ready Preview, Comment Feb 5, 2026 8:34pm
workbench-fastify-workflow Ready Ready Preview, Comment Feb 5, 2026 8:34pm
workbench-hono-workflow Ready Ready Preview, Comment Feb 5, 2026 8:34pm
workbench-nitro-workflow Ready Ready Preview, Comment Feb 5, 2026 8:34pm
workbench-nuxt-workflow Ready Ready Preview, Comment Feb 5, 2026 8:34pm
workbench-sveltekit-workflow Ready Ready Preview, Comment Feb 5, 2026 8:34pm
workbench-vite-workflow Ready Ready Preview, Comment Feb 5, 2026 8:34pm
workflow-docs Ready Ready Preview, Comment Feb 5, 2026 8:34pm
workflow-nest Ready Ready Preview, Comment Feb 5, 2026 8:34pm
workflow-swc-playground Ready Ready Preview, Comment Feb 5, 2026 8:34pm

Copy link
Member Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements end-to-end encryption for workflow user data using AES-256-GCM with per-run key derivation via HKDF-SHA256. The implementation requires client-side runId generation to enable encryption before data serialization.

Changes:

  • Added encryption module with AES-256-GCM + HKDF-SHA256 key derivation
  • Converted all (de)hydration serialization functions to async with encryption support
  • Implemented client-side runId generation for encryption context
  • Updated workflow execution, steps, hooks, and observability for async serialization
  • Added comprehensive encryption test coverage (18 unit tests)

Reviewed changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/world/src/interfaces.ts Added Encryptor, EncryptionContext, and KeyMaterial interfaces; extended World interface
packages/world/src/events.ts Added optional client-provided runId field to run_created event
packages/world-vercel/src/encryption.ts New encryption implementation with HKDF-based per-run key derivation
packages/world-vercel/src/encryption.test.ts Comprehensive test suite for encryption functionality
packages/world-vercel/src/index.ts Integrated encryptor into Vercel World implementation
packages/core/src/serialization.ts Made all serialization functions async; added encryption/decryption helpers
packages/core/src/serialization.test.ts Updated test wrappers for async serialization
packages/core/src/workflow.ts Added world parameter to runWorkflow; updated hydration calls
packages/core/src/workflow.test.ts Updated test helpers with mock world and async wrappers
packages/core/src/step.ts Updated step hydration for async operation
packages/core/src/step.test.ts Updated test helpers with mock world
packages/core/src/workflow/hook.ts Updated hook payload hydration for async operation
packages/core/src/workflow/hook.test.ts Updated test helpers with mock world
packages/core/src/runtime/start.ts Implemented client-side runId generation for encryption
packages/core/src/runtime/start.test.ts Updated mocks to return client-provided runId
packages/core/src/runtime/run.ts Updated result hydration for async operation
packages/core/src/runtime/resume-hook.ts Updated hook metadata hydration for async operation
packages/core/src/runtime/step-handler.ts Updated step I/O serialization for async operation
packages/core/src/runtime/suspension-handler.ts Updated event creation with async serialization
packages/core/src/runtime.ts Pass world instance to runWorkflow
packages/core/src/private.ts Added runId and world to WorkflowOrchestratorContext
packages/core/src/observability.ts Made hydrateResourceIO async with world parameter
packages/core/src/observability.test.ts Updated test helpers with mock world
packages/core/src/writable-stream.test.ts Removed Promise support tests
packages/web-shared/src/api/workflow-server-actions.ts Updated all hydration calls to async with world parameter
packages/cli/src/lib/inspect/output.ts Updated all hydration calls to async with world parameter
Comments suppressed due to low confidence (2)

packages/world/src/interfaces.ts:109

  • The Streamer interface still allows runId: string | Promise<string> for writeToStream, writeToStreamMulti, and closeStream, but WorkflowServerWritableStream now only accepts string (line 393). This could cause confusion for World implementations.

Since runId is now always generated client-side before serialization (as required for encryption), the Streamer interface should be updated to only accept string for consistency. World implementations (world-local, world-postgres, world-vercel) may need to be updated to match this stricter type.

  writeToStream(
    name: string,
    runId: string | Promise<string>,
    chunk: string | Uint8Array
  ): Promise<void>;

  /**
   * Write multiple chunks to a stream in a single operation.
   * This is an optional optimization for world implementations that can
   * batch multiple writes efficiently (e.g., single HTTP request for world-vercel).
   *
   * If not implemented, the caller should fall back to sequential writeToStream() calls.
   *
   * @param name - The stream name
   * @param runId - The run ID (can be a promise)
   * @param chunks - Array of chunks to write, in order
   */
  writeToStreamMulti?(
    name: string,
    runId: string | Promise<string>,
    chunks: (string | Uint8Array)[]
  ): Promise<void>;

  closeStream(name: string, runId: string | Promise<string>): Promise<void>;

packages/core/src/runtime/start.ts:143

  • The client-generated runId is passed in the event data (line 134) but the server implementations (world-local and world-postgres) check the first parameter of events.create() to decide whether to use a client-provided runId or generate one. Since the client passes null as the first parameter (line 130), the server will ignore the client-provided runId in the event data and generate its own runId.

This means encryption will fail because the client encrypted data with one runId but the server will use a different runId when attempting to decrypt.

The fix should be to pass the client-generated runId as the first argument to events.create() instead of null, or update all server implementations to check data.runId from the event data for run_created events.

      const result = await world.events.create(
        null,
        {
          eventType: 'run_created',
          specVersion,
          runId, // Pass client-generated runId to server
          eventData: {
            deploymentId: deploymentId,
            workflowName: workflowName,
            input: workflowArguments,
            executionContext: { traceCarrier, workflowCoreVersion },
          },
        },
        { v1Compat }
      );

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@vercel vercel bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Suggestion:

The hydrateWorkflowReturnValue calls use the old function signature (value, ops, runId) but the function signature was changed to (value, runId, encryptor, ops?, global?, extraRevivers?)

Fix on Vercel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant