Add btoa, atob, and Buffer globals to workflow VM context#1520
Conversation
…ine context Slack-Thread: https://vercel.slack.com/archives/CDSJDH8QH/p1774470115717019?thread_ts=1774470115.717019&cid=CDSJDH8QH Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>
🦋 Changeset detectedLatest commit: 4508ed8 The changes in this PR will be included in the next version bump. This PR includes changesets to release 16 packages
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 |
🧪 E2E Test Results❌ Some tests failed Summary
❌ Failed Tests🌍 Community Worlds (58 failed)mongodb (2 failed):
redis (2 failed):
turso (54 failed):
Details by Category✅ ▲ Vercel Production
✅ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
❌ 🌍 Community Worlds
✅ 📋 Other
❌ Some E2E test jobs failed:
Check the workflow run for details. |
📊 Benchmark Results
workflow with no steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) workflow with 1 step💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) workflow with 10 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) workflow with 25 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) workflow with 50 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) Promise.all with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) Promise.all with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) Promise.all with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) Promise.race with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) Promise.race with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) Promise.race with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) workflow with 10 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) workflow with 25 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) workflow with 50 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) workflow with 10 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) workflow with 25 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) workflow with 50 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) Stream Benchmarks (includes TTFB metrics)workflow with stream💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) stream pipeline with 5 transform steps (1MB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) 10 parallel streams (1MB each)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) fan-out fan-in 10 streams (1MB each)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) SummaryFastest Framework by WorldWinner determined by most benchmark wins
Fastest World by FrameworkWinner determined by most benchmark wins
Column Definitions
Worlds:
❌ Some benchmark jobs failed:
Check the workflow run for details. |
There was a problem hiding this comment.
Pull request overview
Exposes base64 helpers (atob, btoa) and Node’s Buffer in the workflow VM sandbox so workflow code can build/parse base64 payloads (e.g., Basic Auth headers) while running under packages/core’s deterministic VM context.
Changes:
- Add
g.atob/g.btoabindings to the VM context globals. - Add
Bufferto the VM context globals for Node-style base64 and binary manipulation. - Add new unit tests validating base64 encode/decode and Basic Auth-style usage.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| packages/core/src/vm/index.ts | Exposes atob, btoa, and Buffer in the sandboxed vm.Context. |
| packages/core/src/vm/index.test.ts | Adds tests covering atob/btoa/Buffer base64 behavior and a Basic Auth-style scenario. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Node.js Buffer for base64 encoding/decoding and binary data manipulation | ||
| (g as any).Buffer = Buffer; |
There was a problem hiding this comment.
Exposing the full Node Buffer into the deterministic sandbox allows Buffer.allocUnsafe(...) / Buffer(...) / new Buffer(...), which can return uninitialized memory. That is both a potential data-leak vector and a source of non-determinism (buffer contents depend on process heap state), undermining the goal of createContext.
Consider exposing a "safe" Buffer wrapper/proxy that (a) routes numeric allocations through Buffer.alloc (zero-filled), (b) disables/overrides allocUnsafe/allocUnsafeSlow, and ideally (c) throws on deprecated Buffer(number) usage. Add a small test asserting unsafe allocation paths are not available or are deterministic (zero-filled).
| // Node.js Buffer for base64 encoding/decoding and binary data manipulation | |
| (g as any).Buffer = Buffer; | |
| // Node.js Buffer for base64 encoding/decoding and binary data manipulation. | |
| // Expose a "safe" Buffer wrapper to keep the sandbox deterministic and avoid | |
| // uninitialized memory via allocUnsafe / deprecated numeric constructors. | |
| const SafeBuffer: typeof Buffer = new Proxy(Buffer, { | |
| // Handle direct calls like Buffer(...) | |
| apply(target, thisArg, argArray: any[]) { | |
| const [arg0, ...rest] = argArray; | |
| if (typeof arg0 === 'number') { | |
| // Route numeric constructor usage to zero-filled allocation | |
| return Buffer.alloc(arg0); | |
| } | |
| return Reflect.apply(target as any, thisArg, argArray); | |
| }, | |
| // Handle construction like new Buffer(...) | |
| construct(target, argArray: any[], newTarget) { | |
| const [arg0, ...rest] = argArray; | |
| if (typeof arg0 === 'number') { | |
| // Route numeric constructor usage to zero-filled allocation | |
| return Buffer.alloc(arg0); | |
| } | |
| return Reflect.construct(target as any, argArray, newTarget); | |
| }, | |
| get(target, prop, receiver) { | |
| if (prop === 'allocUnsafe' || prop === 'allocUnsafeSlow') { | |
| // Disable unsafe allocation paths inside the deterministic sandbox | |
| return function () { | |
| throw new Error('Buffer allocUnsafe methods are disabled in this sandbox'); | |
| }; | |
| } | |
| return Reflect.get(target as any, prop, receiver); | |
| }, | |
| }); | |
| (g as any).Buffer = SafeBuffer; |
There was a problem hiding this comment.
overkill for now but worth reconsidering in a future update
ea67eec to
4508ed8
Compare
VaguelySerious
left a comment
There was a problem hiding this comment.
LGTM. Copilot might have a point about preventing allocUnsafe, though I personally can't tell. I think you could do a follow-up query to validate that
Enables base64 encoding and decoding inside workflow functions by exposing
btoa,atob, andBufferin the sandboxed VM context.Requested by @Justin Sanford and @Walker Frankenberg for creating basic auth headers in workflows (stream.new migration to WDK).
What changed
g.btoaandg.atobfrom globalThis for Web API base64 encoding/decodingBufferfor Node.js-style base64 operations and binary data manipulation