From 8a8e4e57ea470d6ba491183c055d0a5a521cd342 Mon Sep 17 00:00:00 2001 From: thesmartshadow Date: Thu, 12 Feb 2026 15:02:30 +0000 Subject: [PATCH 1/3] Harden VM context (disable codegen, avoid leaking host console) --- packages/core/src/vm/index.ts | 26 ++++++++++++++++--- packages/core/src/vm/vm-hardening.test.ts | 31 +++++++++++++++++++++++ packages/core/src/workflow.ts | 5 ---- 3 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 packages/core/src/vm/vm-hardening.test.ts diff --git a/packages/core/src/vm/index.ts b/packages/core/src/vm/index.ts index 40ca0b7ec0..3d0c989250 100644 --- a/packages/core/src/vm/index.ts +++ b/packages/core/src/vm/index.ts @@ -19,8 +19,16 @@ export function createContext(options: CreateContextOptions) { let { fixedTimestamp } = options; const { seed } = options; const rng = seedrandom(seed); - const context = vmCreateContext(); - + const context = vmCreateContext(undefined, { + // Hardening: block dynamic code generation inside the VM realm via: + // - eval("..."), new Function("..."), etc. + // NOTE: `node:vm` is not a security sandbox. This reduces common escape primitives + // when executed code is attacker-controlled. + codeGeneration: { + strings: false, + wasm: false, + }, + }); const g: typeof globalThis = runInContext('globalThis', context); // Deterministic `Math.random()` @@ -93,7 +101,19 @@ export function createContext(options: CreateContextOptions) { g.Headers = globalThis.Headers; g.TextEncoder = globalThis.TextEncoder; g.TextDecoder = globalThis.TextDecoder; - g.console = globalThis.console; + // Do NOT leak the host console object into the VM realm by reference. + // Provide a minimal VM-local console stub instead. + const vmConsole = runInContext( + `({ + log(){}, + info(){}, + warn(){}, + error(){}, + debug(){}, + })`, + context + ); + (g as any).console = vmConsole; g.URL = globalThis.URL; g.URLSearchParams = globalThis.URLSearchParams; g.structuredClone = globalThis.structuredClone; diff --git a/packages/core/src/vm/vm-hardening.test.ts b/packages/core/src/vm/vm-hardening.test.ts new file mode 100644 index 0000000000..4d46773067 --- /dev/null +++ b/packages/core/src/vm/vm-hardening.test.ts @@ -0,0 +1,31 @@ +import { runInContext } from 'node:vm'; +import { describe, expect, it } from 'vitest'; +import { createContext } from './index.js'; + +describe('VM hardening (non-weaponized regression tests)', () => { + it('disallows eval and Function constructor inside the VM realm', () => { + const { context } = createContext({ seed: 'poc-seed', fixedTimestamp: 0 }); + + const res = runInContext( + ` + (function(){ + const out = {}; + try { eval("1"); out.eval = "allowed"; } catch { out.eval = "blocked"; } + try { new Function("return 1")(); out.fn = "allowed"; } catch { out.fn = "blocked"; } + return out; + })() + `, + context + ); + + expect(res).toEqual({ eval: 'blocked', fn: 'blocked' }); + }); + + it('does not expose the host console object by reference', () => { + const { globalThis: g } = createContext({ + seed: 'poc-seed', + fixedTimestamp: 0, + }); + expect(g.console).not.toBe(console); + }); +}); diff --git a/packages/core/src/workflow.ts b/packages/core/src/workflow.ts index 3328f3e8b4..6481af4a9e 100644 --- a/packages/core/src/workflow.ts +++ b/packages/core/src/workflow.ts @@ -592,11 +592,6 @@ export async function runWorkflow( } } vmGlobalThis.TransformStream = TransformStream; - - // Eventually we'll probably want to provide our own `console` object, - // but for now we'll just expose the global one. - vmGlobalThis.console = globalThis.console; - // HACK: propagate symbol needed for AI gateway usage const SYMBOL_FOR_REQ_CONTEXT = Symbol.for('@vercel/request-context'); // @ts-expect-error - `@types/node` says symbol is not valid, but it does work From f96bcc285e192cf37de5e5b38295801f8423b815 Mon Sep 17 00:00:00 2001 From: thesmartshadow Date: Thu, 12 Feb 2026 15:25:10 +0000 Subject: [PATCH 2/3] Add changeset for VM hardening Signed-off-by: thesmartshadow --- .changeset/vm-hardening.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/vm-hardening.md diff --git a/.changeset/vm-hardening.md b/.changeset/vm-hardening.md new file mode 100644 index 0000000000..908d5ae161 --- /dev/null +++ b/.changeset/vm-hardening.md @@ -0,0 +1,5 @@ +--- +"@workflow/core": patch +--- + +Harden the VM context by disabling dynamic code generation (eval/new Function/WebAssembly) and preventing leakage of the host console into the VM realm. From 28da161b762820778d17a6284408c18809e1bc6d Mon Sep 17 00:00:00 2001 From: thesmartshadow Date: Thu, 12 Feb 2026 15:27:27 +0000 Subject: [PATCH 3/3] DCO Remediation Commit for thesmartshadow I, thesmartshadow , hereby add my Signed-off-by to this commit: 8a8e4e57ea470d6ba491183c055d0a5a521cd342 I, thesmartshadow , hereby add my Signed-off-by to this commit: 24aca6303c0305b6a4cf454bdd81d7c1ffb6c239 Signed-off-by: thesmartshadow --- .changeset/vm-hardening.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/vm-hardening.md b/.changeset/vm-hardening.md index 908d5ae161..c01634150c 100644 --- a/.changeset/vm-hardening.md +++ b/.changeset/vm-hardening.md @@ -3,3 +3,4 @@ --- Harden the VM context by disabling dynamic code generation (eval/new Function/WebAssembly) and preventing leakage of the host console into the VM realm. +