feat(core): Add IsolatedVmBridge (no-changelog)#2089
Draft
everettbu wants to merge 7 commits intofeat/expression-runtime-pr2-runtime-bundlefrom
Draft
feat(core): Add IsolatedVmBridge (no-changelog)#2089everettbu wants to merge 7 commits intofeat/expression-runtime-pr2-runtime-bundlefrom
everettbu wants to merge 7 commits intofeat/expression-runtime-pr2-runtime-bundlefrom
Conversation
…dependency Introduces the IsolatedVmBridge class that creates a persistent V8 isolate, loads the runtime bundle into it, and exposes three synchronous ivm.Reference callbacks (__getValueAtPath, __getArrayElement, __callFunctionAtPath) for cross-isolate data access. Adds isolated-vm ^6.0.2 as a native dependency. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…atedVmBridge Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ivate method Consistent with loadVendorLibraries() and defineProxySystem() pattern. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…endorLibraries _ and luxon are not exposed on globalThis (removed in PR 2). Check DateTime and extend instead, which are the actual globals the runtime bundle exports. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…wrappers from callbacks Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…th working doc - Remove getDataSync: not in RuntimeBridge interface, no callers - Remove small-array threshold: arrays are always lazy-loaded (length only) - Rename defineProxySystem -> verifyProxySystem (method only verifies) - Update stale comments referencing removed _ and luxon globals - Update initialize() JSDoc steps to reflect current implementation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tate - README: mark PR 2 (runtime bundle) and PR 3 (IsolatedVmBridge) as complete - README: remove stale "(PR 3)" annotations from BridgeConfig - deep-lazy-proxy.md: fix runtime module reference (lazy-proxy.ts, not index.ts) - deep-lazy-proxy.md: expand Related Files to list all four runtime modules - architecture-diagram.mmd: replace stale dataId/Store sequence with actual ivm.Reference callback flow Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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#26142
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
IsolatedVmBridgeis the component that owns the V8 isolate. It:dist/bundle/runtime.iife.jsfrom PR 2) into the isolate context oninitialize()ivm.Referencecallbacks before each evaluation so the in-isolate lazy proxy can fetch workflow data from the host without pre-copying it:__getValueAtPath(path[])— returns a primitive, array metadata{__isArray, __length}, object metadata{__isObject, __keys}, or function metadata__getArrayElement(path[], index)— returns a single element (or its metadata) from an array__callFunctionAtPath(path[], ...args)— invokes a function at the given path in the host data and returns the resultresetDataProxies()in the isolate to initialise$json,$binary,$input, etc. as fresh lazy proxies backed by those callbacksthis === __data, sothis.$json.emailresolves through the proxyArrays are always lazy — only the length is transferred. Elements are fetched individually on demand via
__getArrayElement. There is no small-array threshold.Script compilation is cached — once an expression is compiled to an
ivm.Script, it is stored in aMapkeyed by the expression string. Subsequent evaluations of the same expression skip recompilation.Isolation setup in
initialize()Broken into four focused private methods, each independently verifiable in debug mode:
loadVendorLibraries()— evaluates the runtime bundle in the isolate context and verifiesDateTimeandextendare available onglobalThisverifyProxySystem()— checks thatcreateDeepLazyProxy,resetDataProxies,SafeObject,SafeError, and__dataare all presentinjectErrorHandler()— injects theE()global needed by tournament's try-catch wrapping: re-throws security violations, swallowsTypeErrors (failed prototype-attack attempts), re-throws everything elseNew dependency:
isolated-vm ^6.0.2isolated-vmis a native Node.js addon (compiled from C++ at install time). It is added topnpm.onlyBuiltDependenciesin the rootpackage.jsonsopnpm installcompiles it correctly.Why no tests in this PR?
The interesting behaviour of this bridge is the round-trip: isolate creates a proxy → proxy fires an
ivm.Referencecallback → callback navigates host-side data → value is copied back into the isolate. Unit tests with mocked callbacks would only verify that a mock was called, not the actual serialisation,copy: truetransfer semantics, or the__isArray/__isObjectmetadata protocol.Integration tests covering the full path (bridge creates isolate → loads runtime bundle → sets callbacks → expression evaluates to the correct result) are planned for PR 4, once
ExpressionEvaluatorexists to drive the bridge.Verification
Related Linear tickets, Github issues, and Community forum posts
https://linear.app/n8n/issue/CAT-2311
Review / Merge checklist
release/backport(if the PR is an urgent fix that needs to be backported)