From bc7825dc9371ce1b6efbad867310eceb44903d4a Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 24 Jan 2024 14:02:34 +0100 Subject: [PATCH] Keep isolateRefs in context --- packages/backend-core/src/context/types.ts | 9 +- packages/server/src/jsRunner.ts | 117 +++++++++++---------- 2 files changed, 70 insertions(+), 56 deletions(-) diff --git a/packages/backend-core/src/context/types.ts b/packages/backend-core/src/context/types.ts index 06f75ce6af..cc052ca505 100644 --- a/packages/backend-core/src/context/types.ts +++ b/packages/backend-core/src/context/types.ts @@ -1,5 +1,5 @@ import { IdentityContext } from "@budibase/types" -import { Isolate, Context } from "isolated-vm" +import { Isolate, Context, Module } from "isolated-vm" // keep this out of Budibase types, don't want to expose context info export type ContextMap = { @@ -10,6 +10,9 @@ export type ContextMap = { isScim?: boolean automationId?: string isMigrating?: boolean - jsIsolate?: Isolate - jsContext?: Context + isolateRefs?: { + jsIsolate: Isolate + jsContext: Context + helpersModule: Module + } } diff --git a/packages/server/src/jsRunner.ts b/packages/server/src/jsRunner.ts index 7c965e5c8c..43a128f418 100644 --- a/packages/server/src/jsRunner.ts +++ b/packages/server/src/jsRunner.ts @@ -7,18 +7,22 @@ import fs from "fs" import url from "url" import crypto from "crypto" +const helpersSource = fs.readFileSync( + `${require.resolve("@budibase/string-templates/index-helpers")}`, + "utf8" +) + export function init() { - const helpersSource = fs.readFileSync( - `${require.resolve("@budibase/string-templates/index-helpers")}`, - "utf8" - ) setJSRunner((js: string, ctx: Record) => { return tracer.trace("runJS", {}, span => { - const bbCtx = context.getCurrentContext() || {} - let { jsIsolate = new ivm.Isolate({ memoryLimit: 64 }) } = bbCtx - let { jsContext = jsIsolate.createContextSync() } = bbCtx + const bbCtx = context.getCurrentContext()! - const injectedRequire = `const require = function(val){ + const isolateRefs = bbCtx.isolateRefs + if (!isolateRefs) { + const jsIsolate = new ivm.Isolate({ memoryLimit: 64 }) + const jsContext = jsIsolate.createContextSync() + + const injectedRequire = `const require = function(val){ switch (val) { case "url": return { @@ -28,63 +32,70 @@ export function init() { } };` - const global = jsContext.global - global.setSync( - "urlResolveCb", - new ivm.Callback((...params: Parameters) => - url.resolve(...params) + const global = jsContext.global + global.setSync( + "urlResolveCb", + new ivm.Callback((...params: Parameters) => + url.resolve(...params) + ) ) - ) - global.setSync( - "urlParseCb", - new ivm.Callback((...params: Parameters) => - url.parse(...params) + global.setSync( + "urlParseCb", + new ivm.Callback((...params: Parameters) => + url.parse(...params) + ) ) - ) - const helpersModule = jsIsolate.compileModuleSync( - `${injectedRequire};${helpersSource}` - ) + const helpersModule = jsIsolate.compileModuleSync( + `${injectedRequire};${helpersSource}` + ) - const cryptoModule = jsIsolate.compileModuleSync(`export default { + const cryptoModule = jsIsolate.compileModuleSync(`export default { randomUUID: cryptoRandomUUIDCb, }`) - cryptoModule.instantiateSync(jsContext, specifier => { - throw new Error(`No imports allowed. Required: ${specifier}`) - }) - - global.setSync( - "cryptoRandomUUIDCb", - new ivm.Callback((...params: Parameters) => { - return crypto.randomUUID(...params) + cryptoModule.instantiateSync(jsContext, specifier => { + throw new Error(`No imports allowed. Required: ${specifier}`) }) - ) - helpersModule.instantiateSync(jsContext, specifier => { - if (specifier === "crypto") { - return cryptoModule - } - throw new Error(`No imports allowed. Required: ${specifier}`) - }) - - const perRequestLimit = env.JS_PER_REQUEST_TIME_LIMIT_MS - if (perRequestLimit) { - const cpuMs = Number(jsIsolate.cpuTime) / 1e6 - if (cpuMs > perRequestLimit) { - throw new Error( - `CPU time limit exceeded (${cpuMs}ms > ${perRequestLimit}ms)` + global.setSync( + "cryptoRandomUUIDCb", + new ivm.Callback( + (...params: Parameters) => { + return crypto.randomUUID(...params) + } ) + ) + + helpersModule.instantiateSync(jsContext, specifier => { + if (specifier === "crypto") { + return cryptoModule + } + throw new Error(`No imports allowed. Required: ${specifier}`) + }) + + const perRequestLimit = env.JS_PER_REQUEST_TIME_LIMIT_MS + if (perRequestLimit) { + const cpuMs = Number(jsIsolate.cpuTime) / 1e6 + if (cpuMs > perRequestLimit) { + throw new Error( + `CPU time limit exceeded (${cpuMs}ms > ${perRequestLimit}ms)` + ) + } } + + for (const [key, value] of Object.entries(ctx)) { + if (key === "helpers") { + // Can't copy the native helpers into the isolate. We just ignore them as they are handled properly from the helpersSource + continue + } + global.setSync(key, value) + } + + bbCtx.isolateRefs = { jsContext, jsIsolate, helpersModule } } - for (const [key, value] of Object.entries(ctx)) { - if (key === "helpers") { - // Can't copy the native helpers into the isolate. We just ignore them as they are handled properly from the helpersSource - continue - } - global.setSync(key, value) - } + let { jsIsolate, jsContext, helpersModule } = bbCtx.isolateRefs! const script = jsIsolate.compileModuleSync( `import helpers from "compiled_module";${js};cb(run());`, @@ -100,7 +111,7 @@ export function init() { }) let result - global.setSync( + jsContext.global.setSync( "cb", new ivm.Callback((value: any) => { result = value