feat(core): Add runtime bundle and extension utilities#2036
Open
everettbu wants to merge 6 commits into
Open
Conversation
…ties - Add esbuild.config.js to bundle src/runtime/index.ts as both IIFE (for isolated-vm) and ESM (for Web Workers) into dist/bundle/ - Add src/runtime/index.ts: deep lazy proxy system, SafeObject/SafeError wrappers, resetDataProxies(), and library globals for the isolate - Add src/extensions/: full copy of the n8n-workflow extension utilities (array, boolean, date, number, object, string, expression-extension-error, extend, extensions, utils) bundled into the runtime IIFE - Prepend 7-line mirror comment to packages/workflow/src/extensions/*.ts documenting the intentional duplication and the path to eliminate it - Restore build:runtime script in package.json The runtime bundle is self-contained (lodash and luxon are inlined by esbuild) and provides the expression evaluation environment loaded into each isolated-vm context by the bridge (added in PR 3). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d modules - safe-globals.ts: SafeObject, SafeError, ExpressionError, __sanitize - lazy-proxy.ts: createDeepLazyProxy - reset.ts: resetDataProxies - index.ts: global declarations, library setup, wires everything to globalThis esbuild bundles all modules regardless; the split is purely for readability. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d globals Remove unused imports (Duration, Interval, lodash) and drop the corresponding globalThis assignments for _, Duration, Interval, and luxon namespace — these are not exposed by the current expression engine. Update comments in index.ts and reset.ts to match. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
everettbu
marked this pull request as ready for review
February 21, 2026 16:42
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…-API parameters (#26053)
…ure docs - Remove getDataSync() reference from security section of ARCHITECTURE.md; data access from the isolate uses ivm.Reference callbacks, not a method on RuntimeBridge - Replace NodeVmBridge mentions (does not exist) with accurate references to IsolatedVmBridge integration tests - Remove small-array threshold from deep-lazy-proxy.md test coverage list; all arrays are always lazy-loaded via __getArrayElement callbacks - Remove stale manual-test.example.ts reference (file does not exist) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
everettbu
force-pushed
the
feat/expression-runtime-pr2-runtime-bundle
branch
from
February 23, 2026 12:29
7f24fdf to
576b06b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Mirror of n8n-io/n8n#26077
Original author: despairblue
Context
n8n expressions today run via
tournament(an AST transformer) inside the main Node.js process. The goal of this PR series is to make expression evaluation available insideisolated-vm— a V8 isolate that is memory-isolated from the host process. This gives us a proper security boundary: a malicious expression cannot access Node.js APIs, the filesystem, or the host's memory.The series is being landed incrementally:
IsolatedVmBridge— creates the isolate, loads the bundle, and exposes workflow data to it via callbacksExpressionEvaluator— the public API that drives the bridge, plus integration testsWhat this PR adds
When an expression is evaluated inside an isolate, the isolate has no access to the host process — it cannot
require()anything or call Node.js APIs directly. Every piece of code it needs must be bundled into a single self-contained script and injected at isolate startup.This PR produces that script.
esbuild.config.jsbuildssrc/runtime/index.tsand all its imports into two bundle formats:dist/bundle/runtime.iife.js— an IIFE loaded into the isolate as a string viaisolated-vmdist/bundle/runtime.esm.js— an ESM module for future Web Worker supportsrc/runtime/— the code that runs inside the isolate, split into four focused modules:safe-globals.ts—SafeObjectandSafeErrorproxies that block dangerous methods (prototype pollution, stack trace manipulation) from expression code;ExpressionErrorfor tournament-generated error handlers;__sanitize()for dynamic property access (guards againstobj["__proto__"]etc.)lazy-proxy.ts—createDeepLazyProxy(), aProxythat loads workflow data on demand across the isolate boundary. When an expression accesses$json.name, the proxy calls anivm.Referencecallback set by the bridge (PR 3) to fetch just that value — nothing is copied up front.reset.ts—resetDataProxies(), called by the bridge before each expression evaluation to clear proxy caches and re-expose$json,$binary,$input, etc. toglobalThis.index.ts— entry point: declares global types, exposesextend/extendOptionalandDateTimefor tournament-transformed expressions, and wires the above modules toglobalThis.src/extensions/— see the duplication note below.packages/workflow/src/extensions/*.ts— a 7-line comment prepended to each file documenting the mirror.Why is
src/extensions/a copy ofpackages/workflow/src/extensions/?n8n expressions support method calls like
$json.name.toUpperCase()or[1,2,3].sum(). The non-native ones (sum,toDateTime, etc.) are provided by the extension framework inpackages/workflow/src/extensions/. The isolate needs these too — but it cannot import them from the host, because all isolate code must be pre-bundled.The natural fix would be to import them from
@n8n`/expression-runtime` and have esbuild bundle them in. The problem is that@n8n/expression-runtimeis Vite-aliased to an empty stub for browser builds, to exclude theisolated-vmnative addon. Vite analyses dynamic imports statically — even aIS_FRONTEND-guardedimport('@n8n/expression-runtime')inpackages/workflowwould cause the browser build to pull in the stub, meaning the extension functions would be missing at runtime in the editor.The workaround is to keep a verbatim copy of the extension files inside
@n8n`/expression-runtime/src/extensions/`, which esbuild bundles directly into the IIFE. Each mirrored file in `packages/workflow/src/extensions/` has a comment explaining this and pointing to the long-term fix: split@n8n/expression-runtimeinto a browser-safe subpath (extension utilities only, noisolated-vm) and a node-only subpath (the bridge and evaluator, Vite-stubbed). At that point,packages/workflowcan import the extensions directly from the package and the duplication goes away.Why no tests in this PR?
src/extensions/— already covered by the existing test suite inpackages/workflow/src/extensions/. Duplicating it would mean keeping two suites in sync.src/runtime/— the interesting behaviour (createDeepLazyProxy,resetDataProxies) is the cross-isolate data transfer: the proxy callsivm.Referencecallbacks set by the bridge, which live in the host process. That transfer cannot happen without an actual isolate. Unit tests with mocked callbacks would only test that a mock was called — they would not exercise the serialisation, thecopy: truetransfer semantics, or the metadata protocol (__isArray,__isObject,__isFunction) that the bridge and runtime agree on.The integration tests that cover the full path — bridge creates isolate → loads runtime bundle → sets callbacks → expression evaluates and produces the correct result — are planned for PR 4, once both the bridge and the evaluator exist.
Verification
Related Linear tickets, Github issues, and Community forum posts
Review / Merge checklist
release/backport(if the PR is an urgent fix that needs to be backported)