From 218ba1d2831f041f6c958f2d7e37969e135c6bd6 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 7 Feb 2024 18:07:05 +0100 Subject: [PATCH 01/16] VM types --- packages/backend-core/src/context/types.ts | 3 ++- packages/types/src/sdk/index.ts | 1 + packages/types/src/sdk/vm.ts | 4 ++++ 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 packages/types/src/sdk/vm.ts diff --git a/packages/backend-core/src/context/types.ts b/packages/backend-core/src/context/types.ts index cc052ca505..75f5234651 100644 --- a/packages/backend-core/src/context/types.ts +++ b/packages/backend-core/src/context/types.ts @@ -1,4 +1,4 @@ -import { IdentityContext } from "@budibase/types" +import { IdentityContext, VM } from "@budibase/types" import { Isolate, Context, Module } from "isolated-vm" // keep this out of Budibase types, don't want to expose context info @@ -15,4 +15,5 @@ export type ContextMap = { jsContext: Context helpersModule: Module } + vm?: VM } diff --git a/packages/types/src/sdk/index.ts b/packages/types/src/sdk/index.ts index 0eab2ba556..36faaae9c3 100644 --- a/packages/types/src/sdk/index.ts +++ b/packages/types/src/sdk/index.ts @@ -20,3 +20,4 @@ export * from "./cli" export * from "./websocket" export * from "./permissions" export * from "./row" +export * from "./vm" diff --git a/packages/types/src/sdk/vm.ts b/packages/types/src/sdk/vm.ts new file mode 100644 index 0000000000..0c85cac925 --- /dev/null +++ b/packages/types/src/sdk/vm.ts @@ -0,0 +1,4 @@ +export interface VM { + cpuTime: bigint + execute(code: string): string +} From c5abb4f8462812b086d680fbaf9bbf4a09ca3c05 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 7 Feb 2024 18:07:23 +0100 Subject: [PATCH 02/16] Create wrapper --- packages/server/src/jsRunner/vm/index.ts | 154 +++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 packages/server/src/jsRunner/vm/index.ts diff --git a/packages/server/src/jsRunner/vm/index.ts b/packages/server/src/jsRunner/vm/index.ts new file mode 100644 index 0000000000..0f9b11e531 --- /dev/null +++ b/packages/server/src/jsRunner/vm/index.ts @@ -0,0 +1,154 @@ +import ivm from "isolated-vm" + +import url from "url" +import crypto from "crypto" +import querystring from "querystring" + +import { BundleType, loadBundle } from "../bundles" +import { VM } from "@budibase/types" + +export class IsolatedVM implements VM { + #isolate: ivm.Isolate + #vm: ivm.Context + #jail: ivm.Reference + #timeout: number + + #modules: Record< + string, + { + headCode: string + module: ivm.Module + } + > = {} + + readonly #resultKey = "results" + + constructor({ + memoryLimit, + timeout, + }: { + memoryLimit: number + timeout: number + }) { + this.#isolate = new ivm.Isolate({ memoryLimit }) + this.#vm = this.#isolate.createContextSync() + this.#jail = this.#vm.global + this.#jail.setSync("global", this.#jail.derefInto()) + + this.#addToContext({ + [this.#resultKey]: { out: "" }, + }) + + this.#timeout = timeout + } + + get cpuTime() { + return this.#isolate.cpuTime + } + + withHelpers() { + const injectedRequire = ` + const require = function(val){ + switch (val) { + case "url": + return { + resolve: (...params) => urlResolveCb(...params), + parse: (...params) => urlParseCb(...params), + } + case "querystring": + return { + escape: (...params) => querystringEscapeCb(...params), + } + } + };` + + const helpersSource = loadBundle(BundleType.HELPERS) + const helpersModule = this.#isolate.compileModuleSync( + `${injectedRequire};${helpersSource}` + ) + + this.#addToContext({ + urlResolveCb: new ivm.Callback( + (...params: Parameters) => url.resolve(...params) + ), + urlParseCb: new ivm.Callback((...params: Parameters) => + url.parse(...params) + ), + querystringEscapeCb: new ivm.Callback( + (...params: Parameters) => + querystring.escape(...params) + ), + helpersStripProtocol: new ivm.Callback((str: string) => { + var parsed = url.parse(str) as any + parsed.protocol = "" + return parsed.format() + }), + }) + + const cryptoModule = this.#isolate.compileModuleSync( + `export default { randomUUID: cryptoRandomUUIDCb }` + ) + cryptoModule.instantiateSync(this.#vm, specifier => { + throw new Error(`No imports allowed. Required: ${specifier}`) + }) + + this.#addToContext({ + cryptoRandomUUIDCb: new ivm.Callback( + (...params: Parameters) => { + return crypto.randomUUID(...params) + } + ), + }) + + helpersModule.instantiateSync(this.#vm, specifier => { + if (specifier === "crypto") { + return cryptoModule + } + throw new Error(`No imports allowed. Required: ${specifier}`) + }) + + this.#modules["compiled_module"] = { + headCode: 'import helpers from "compiled_module"', + module: helpersModule, + } + return this + } + + execute(code: string): string { + code = [ + ...Object.values(this.#modules).map(m => m.headCode), + `results.out=${code};`, + ].join(";") + + const script = this.#isolate.compileModuleSync(code) + + script.instantiateSync(this.#vm, specifier => { + if (specifier === "compiled_module") { + return this.#modules[specifier].module + } + + throw new Error(`"${specifier}" import not allowed`) + }) + + script.evaluateSync({ timeout: this.#timeout }) + + const result = this.#getResult() + return result + } + + #addToContext(context: Record) { + for (let key in context) { + this.#jail.setSync( + key, + new ivm.ExternalCopy(context[key]).copyInto({ release: true }) + ) + } + } + + #getResult() { + const ref = this.#vm.global.getSync(this.#resultKey, { reference: true }) + const result = ref.copySync() + ref.release() + return result.out + } +} From 3b8b60aa037be0af9d3ca670ae0fb5a8b631f03d Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 7 Feb 2024 18:08:15 +0100 Subject: [PATCH 03/16] Use wrapper --- packages/backend-core/src/context/types.ts | 6 - packages/server/src/jsRunner/index.ts | 133 ++------------------- 2 files changed, 11 insertions(+), 128 deletions(-) diff --git a/packages/backend-core/src/context/types.ts b/packages/backend-core/src/context/types.ts index 75f5234651..0f4c2106d0 100644 --- a/packages/backend-core/src/context/types.ts +++ b/packages/backend-core/src/context/types.ts @@ -1,5 +1,4 @@ import { IdentityContext, VM } from "@budibase/types" -import { Isolate, Context, Module } from "isolated-vm" // keep this out of Budibase types, don't want to expose context info export type ContextMap = { @@ -10,10 +9,5 @@ export type ContextMap = { isScim?: boolean automationId?: string isMigrating?: boolean - isolateRefs?: { - jsIsolate: Isolate - jsContext: Context - helpersModule: Module - } vm?: VM } diff --git a/packages/server/src/jsRunner/index.ts b/packages/server/src/jsRunner/index.ts index 9c54779567..140881aa21 100644 --- a/packages/server/src/jsRunner/index.ts +++ b/packages/server/src/jsRunner/index.ts @@ -1,12 +1,9 @@ -import ivm from "isolated-vm" import env from "../environment" import { setJSRunner, JsErrorTimeout } from "@budibase/string-templates" -import { context } from "@budibase/backend-core" import tracer from "dd-trace" -import url from "url" -import crypto from "crypto" -import querystring from "querystring" -import { BundleType, loadBundle } from "./bundles" + +import { IsolatedVM } from "./vm" +import { context } from "@budibase/backend-core" class ExecutionTimeoutError extends Error { constructor(message: string) { @@ -16,109 +13,24 @@ class ExecutionTimeoutError extends Error { } export function init() { - const helpersSource = loadBundle(BundleType.HELPERS) setJSRunner((js: string, ctx: Record) => { return tracer.trace("runJS", {}, span => { try { const bbCtx = context.getCurrentContext()! - const isolateRefs = bbCtx.isolateRefs - if (!isolateRefs) { - const jsIsolate = new ivm.Isolate({ + let { vm } = bbCtx + if (!vm) { + vm = new IsolatedVM({ memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, - }) - const jsContext = jsIsolate.createContextSync() + timeout: env.JS_PER_EXECUTION_TIME_LIMIT_MS, + }).withHelpers() - const injectedRequire = ` - const require = function(val){ - switch (val) { - case "url": - return { - resolve: (...params) => urlResolveCb(...params), - parse: (...params) => urlParseCb(...params), - } - case "querystring": - return { - escape: (...params) => querystringEscapeCb(...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( - "querystringEscapeCb", - new ivm.Callback( - (...params: Parameters) => - querystring.escape(...params) - ) - ) - - global.setSync( - "helpersStripProtocol", - new ivm.Callback((str: string) => { - var parsed = url.parse(str) as any - parsed.protocol = "" - return parsed.format() - }) - ) - - const helpersModule = jsIsolate.compileModuleSync( - `${injectedRequire};${helpersSource}` - ) - - 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) - } - ) - ) - - helpersModule.instantiateSync(jsContext, specifier => { - if (specifier === "crypto") { - return cryptoModule - } - throw new Error(`No imports allowed. Required: ${specifier}`) - }) - - 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 } + bbCtx.vm = vm } - let { jsIsolate, jsContext, helpersModule } = bbCtx.isolateRefs! - const perRequestLimit = env.JS_PER_REQUEST_TIME_LIMIT_MS if (perRequestLimit) { - const cpuMs = Number(jsIsolate.cpuTime) / 1e6 + const cpuMs = Number(vm.cpuTime) / 1e6 if (cpuMs > perRequestLimit) { throw new ExecutionTimeoutError( `CPU time limit exceeded (${cpuMs}ms > ${perRequestLimit}ms)` @@ -126,30 +38,7 @@ export function init() { } } - const script = jsIsolate.compileModuleSync( - `import helpers from "compiled_module";const result=${js};cb(result)`, - {} - ) - - script.instantiateSync(jsContext, specifier => { - if (specifier === "compiled_module") { - return helpersModule - } - - throw new Error(`"${specifier}" import not allowed`) - }) - - let result - jsContext.global.setSync( - "cb", - new ivm.Callback((value: any) => { - result = value - }) - ) - - script.evaluateSync({ - timeout: env.JS_PER_EXECUTION_TIME_LIMIT_MS, - }) + const result = vm.execute(js) return result } catch (error: any) { From 0d0171fa086e3a5ce875db0ea18e99c9785ff78b Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 7 Feb 2024 18:12:14 +0100 Subject: [PATCH 04/16] Move cpulimits responsability --- packages/server/src/jsRunner/index.ts | 17 ---------------- packages/server/src/jsRunner/vm/index.ts | 26 ++++++++++++++++++++---- packages/types/src/sdk/vm.ts | 1 - 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/server/src/jsRunner/index.ts b/packages/server/src/jsRunner/index.ts index 140881aa21..aab5a64098 100644 --- a/packages/server/src/jsRunner/index.ts +++ b/packages/server/src/jsRunner/index.ts @@ -5,13 +5,6 @@ import tracer from "dd-trace" import { IsolatedVM } from "./vm" import { context } from "@budibase/backend-core" -class ExecutionTimeoutError extends Error { - constructor(message: string) { - super(message) - this.name = "ExecutionTimeoutError" - } -} - export function init() { setJSRunner((js: string, ctx: Record) => { return tracer.trace("runJS", {}, span => { @@ -28,16 +21,6 @@ export function init() { bbCtx.vm = vm } - const perRequestLimit = env.JS_PER_REQUEST_TIME_LIMIT_MS - if (perRequestLimit) { - const cpuMs = Number(vm.cpuTime) / 1e6 - if (cpuMs > perRequestLimit) { - throw new ExecutionTimeoutError( - `CPU time limit exceeded (${cpuMs}ms > ${perRequestLimit}ms)` - ) - } - } - const result = vm.execute(js) return result diff --git a/packages/server/src/jsRunner/vm/index.ts b/packages/server/src/jsRunner/vm/index.ts index 0f9b11e531..150456ad03 100644 --- a/packages/server/src/jsRunner/vm/index.ts +++ b/packages/server/src/jsRunner/vm/index.ts @@ -7,11 +7,19 @@ import querystring from "querystring" import { BundleType, loadBundle } from "../bundles" import { VM } from "@budibase/types" +class ExecutionTimeoutError extends Error { + constructor(message: string) { + super(message) + this.name = "ExecutionTimeoutError" + } +} + export class IsolatedVM implements VM { #isolate: ivm.Isolate #vm: ivm.Context #jail: ivm.Reference #timeout: number + #perRequestLimit?: number #modules: Record< string, @@ -26,9 +34,11 @@ export class IsolatedVM implements VM { constructor({ memoryLimit, timeout, + perRequestLimit, }: { memoryLimit: number timeout: number + perRequestLimit?: number }) { this.#isolate = new ivm.Isolate({ memoryLimit }) this.#vm = this.#isolate.createContextSync() @@ -40,10 +50,7 @@ export class IsolatedVM implements VM { }) this.#timeout = timeout - } - - get cpuTime() { - return this.#isolate.cpuTime + this.#perRequestLimit = perRequestLimit } withHelpers() { @@ -115,6 +122,17 @@ export class IsolatedVM implements VM { } execute(code: string): string { + const perRequestLimit = this.#perRequestLimit + + if (perRequestLimit) { + const cpuMs = Number(this.#isolate.cpuTime) / 1e6 + if (cpuMs > perRequestLimit) { + throw new ExecutionTimeoutError( + `CPU time limit exceeded (${cpuMs}ms > ${perRequestLimit}ms)` + ) + } + } + code = [ ...Object.values(this.#modules).map(m => m.headCode), `results.out=${code};`, diff --git a/packages/types/src/sdk/vm.ts b/packages/types/src/sdk/vm.ts index 0c85cac925..3abec4d39d 100644 --- a/packages/types/src/sdk/vm.ts +++ b/packages/types/src/sdk/vm.ts @@ -1,4 +1,3 @@ export interface VM { - cpuTime: bigint execute(code: string): string } From c44119b3f9a5a7442cf4819e2f55dd9c341748d2 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 8 Feb 2024 00:34:23 +0100 Subject: [PATCH 05/16] Callbacks --- packages/server/src/jsRunner/vm/index.ts | 88 +++++++++++++----------- 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/packages/server/src/jsRunner/vm/index.ts b/packages/server/src/jsRunner/vm/index.ts index 150456ad03..6723673b07 100644 --- a/packages/server/src/jsRunner/vm/index.ts +++ b/packages/server/src/jsRunner/vm/index.ts @@ -54,37 +54,23 @@ export class IsolatedVM implements VM { } withHelpers() { - const injectedRequire = ` - const require = function(val){ - switch (val) { - case "url": - return { - resolve: (...params) => urlResolveCb(...params), - parse: (...params) => urlParseCb(...params), - } - case "querystring": - return { - escape: (...params) => querystringEscapeCb(...params), - } - } - };` + const urlModule = this.#registerCallbacks({ + resolve: url.resolve, + parse: url.parse, + }) - const helpersSource = loadBundle(BundleType.HELPERS) - const helpersModule = this.#isolate.compileModuleSync( - `${injectedRequire};${helpersSource}` - ) + const querystringModule = this.#registerCallbacks({ + escape: querystring.escape, + }) + + const injectedRequire = `const require=function req(val) { + switch (val) { + case "url": return ${urlModule}; + case "querystring": return ${querystringModule}; + } + }` this.#addToContext({ - urlResolveCb: new ivm.Callback( - (...params: Parameters) => url.resolve(...params) - ), - urlParseCb: new ivm.Callback((...params: Parameters) => - url.parse(...params) - ), - querystringEscapeCb: new ivm.Callback( - (...params: Parameters) => - querystring.escape(...params) - ), helpersStripProtocol: new ivm.Callback((str: string) => { var parsed = url.parse(str) as any parsed.protocol = "" @@ -92,24 +78,24 @@ export class IsolatedVM implements VM { }), }) - const cryptoModule = this.#isolate.compileModuleSync( - `export default { randomUUID: cryptoRandomUUIDCb }` + const helpersSource = loadBundle(BundleType.HELPERS) + const helpersModule = this.#isolate.compileModuleSync( + `${injectedRequire};${helpersSource}` ) - cryptoModule.instantiateSync(this.#vm, specifier => { - throw new Error(`No imports allowed. Required: ${specifier}`) - }) - this.#addToContext({ - cryptoRandomUUIDCb: new ivm.Callback( - (...params: Parameters) => { - return crypto.randomUUID(...params) - } - ), + const cryptoModule = this.#registerCallbacks({ + randomUUID: crypto.randomUUID, }) helpersModule.instantiateSync(this.#vm, specifier => { if (specifier === "crypto") { - return cryptoModule + const module = this.#isolate.compileModuleSync( + `export default ${cryptoModule}` + ) + module.instantiateSync(this.#vm, specifier => { + throw new Error(`No imports allowed. Required: ${specifier}`) + }) + return module } throw new Error(`No imports allowed. Required: ${specifier}`) }) @@ -154,6 +140,28 @@ export class IsolatedVM implements VM { return result } + #registerCallbacks(functions: Record) { + const libId = crypto.randomUUID().replace(/-/g, "") + + const x: Record = {} + for (const [funcName, func] of Object.entries(functions)) { + const key = `f${libId}${funcName}cb` + x[funcName] = key + + this.#addToContext({ + [key]: new ivm.Callback((...params: any[]) => (func as any)(...params)), + }) + } + + const mod = + `{` + + Object.entries(x) + .map(([key, func]) => `${key}: ${func}`) + .join() + + "}" + return mod + } + #addToContext(context: Record) { for (let key in context) { this.#jail.setSync( From 7693a1fc6945944ef1185ce50da1511095318850 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 8 Feb 2024 00:43:13 +0100 Subject: [PATCH 06/16] Fix imports --- packages/server/src/jsRunner/vm/index.ts | 46 ++++++++++++------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/packages/server/src/jsRunner/vm/index.ts b/packages/server/src/jsRunner/vm/index.ts index 6723673b07..c05d9c03f2 100644 --- a/packages/server/src/jsRunner/vm/index.ts +++ b/packages/server/src/jsRunner/vm/index.ts @@ -21,13 +21,11 @@ export class IsolatedVM implements VM { #timeout: number #perRequestLimit?: number - #modules: Record< - string, - { - headCode: string - module: ivm.Module - } - > = {} + #modules: { + import: string + moduleKey: string + module: ivm.Module + }[] = [] readonly #resultKey = "results" @@ -63,13 +61,6 @@ export class IsolatedVM implements VM { escape: querystring.escape, }) - const injectedRequire = `const require=function req(val) { - switch (val) { - case "url": return ${urlModule}; - case "querystring": return ${querystringModule}; - } - }` - this.#addToContext({ helpersStripProtocol: new ivm.Callback((str: string) => { var parsed = url.parse(str) as any @@ -78,17 +69,22 @@ export class IsolatedVM implements VM { }), }) + const injectedRequire = `const require=function req(val) { + switch (val) { + case "url": return ${urlModule}; + case "querystring": return ${querystringModule}; + } + }` const helpersSource = loadBundle(BundleType.HELPERS) const helpersModule = this.#isolate.compileModuleSync( `${injectedRequire};${helpersSource}` ) - const cryptoModule = this.#registerCallbacks({ - randomUUID: crypto.randomUUID, - }) - helpersModule.instantiateSync(this.#vm, specifier => { if (specifier === "crypto") { + const cryptoModule = this.#registerCallbacks({ + randomUUID: crypto.randomUUID, + }) const module = this.#isolate.compileModuleSync( `export default ${cryptoModule}` ) @@ -100,10 +96,11 @@ export class IsolatedVM implements VM { throw new Error(`No imports allowed. Required: ${specifier}`) }) - this.#modules["compiled_module"] = { - headCode: 'import helpers from "compiled_module"', + this.#modules.push({ + import: "helpers", + moduleKey: `i${crypto.randomUUID().replace(/-/g, "")}`, module: helpersModule, - } + }) return this } @@ -120,15 +117,16 @@ export class IsolatedVM implements VM { } code = [ - ...Object.values(this.#modules).map(m => m.headCode), + ...this.#modules.map(m => `import ${m.import} from "${m.moduleKey}"`), `results.out=${code};`, ].join(";") const script = this.#isolate.compileModuleSync(code) script.instantiateSync(this.#vm, specifier => { - if (specifier === "compiled_module") { - return this.#modules[specifier].module + const module = this.#modules.find(m => m.moduleKey === specifier) + if (module) { + return module.module } throw new Error(`"${specifier}" import not allowed`) From e4285e30f1cbb00dab89debfc3ee4f455b4db815 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 8 Feb 2024 00:46:50 +0100 Subject: [PATCH 07/16] Use wrapper for queries --- packages/server/src/utilities/scriptRunner.ts | 61 +++---------------- 1 file changed, 10 insertions(+), 51 deletions(-) diff --git a/packages/server/src/utilities/scriptRunner.ts b/packages/server/src/utilities/scriptRunner.ts index b6e597cc55..c89d266b7e 100644 --- a/packages/server/src/utilities/scriptRunner.ts +++ b/packages/server/src/utilities/scriptRunner.ts @@ -1,63 +1,22 @@ -import ivm from "isolated-vm" +import env from "../environment" +import { IsolatedVM } from "../jsRunner/vm" const JS_TIMEOUT_MS = 1000 - class ScriptRunner { - vm: IsolatedVM + #code constructor(script: string, context: any) { - const code = `let fn = () => {\n${script}\n}; results.out = fn();` - this.vm = new IsolatedVM({ memoryLimit: 8 }) - this.vm.context = { - ...context, - results: { out: "" }, - } - this.vm.code = code + this.#code = `let fn = () => {\n${script}\n}; results.out = fn();` } execute() { - this.vm.runScript() - const results = this.vm.getValue("results") - return results.out - } -} - -class IsolatedVM { - isolate: ivm.Isolate - vm: ivm.Context - #jail: ivm.Reference - script: any - - constructor({ memoryLimit }: { memoryLimit: number }) { - this.isolate = new ivm.Isolate({ memoryLimit }) - this.vm = this.isolate.createContextSync() - this.#jail = this.vm.global - this.#jail.setSync("global", this.#jail.derefInto()) - } - - getValue(key: string) { - const ref = this.vm.global.getSync(key, { reference: true }) - const result = ref.copySync() - ref.release() + const vm = new IsolatedVM({ + memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, + timeout: JS_TIMEOUT_MS, + }).withHelpers() + const result = vm.execute(this.#code) return result } - - set context(context: Record) { - for (let key in context) { - this.#jail.setSync(key, this.copyRefToVm(context[key])) - } - } - - set code(code: string) { - this.script = this.isolate.compileScriptSync(code) - } - - runScript() { - this.script.runSync(this.vm, { timeout: JS_TIMEOUT_MS }) - } - - copyRefToVm(value: Object): ivm.Copy { - return new ivm.ExternalCopy(value).copyInto({ release: true }) - } } + export default ScriptRunner From 008b39abf44ce218f897a258ee5e9fc97ac758ba Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 8 Feb 2024 00:54:03 +0100 Subject: [PATCH 08/16] Use wrapper for scripts --- packages/server/src/jsRunner/vm/index.ts | 5 +++++ packages/server/src/utilities/scriptRunner.ts | 13 +++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/server/src/jsRunner/vm/index.ts b/packages/server/src/jsRunner/vm/index.ts index c05d9c03f2..3cdf05b873 100644 --- a/packages/server/src/jsRunner/vm/index.ts +++ b/packages/server/src/jsRunner/vm/index.ts @@ -104,6 +104,11 @@ export class IsolatedVM implements VM { return this } + withContext(context: Record) { + this.#addToContext(context) + return this + } + execute(code: string): string { const perRequestLimit = this.#perRequestLimit diff --git a/packages/server/src/utilities/scriptRunner.ts b/packages/server/src/utilities/scriptRunner.ts index c89d266b7e..597c4269ac 100644 --- a/packages/server/src/utilities/scriptRunner.ts +++ b/packages/server/src/utilities/scriptRunner.ts @@ -4,17 +4,18 @@ import { IsolatedVM } from "../jsRunner/vm" const JS_TIMEOUT_MS = 1000 class ScriptRunner { #code + #vm constructor(script: string, context: any) { - this.#code = `let fn = () => {\n${script}\n}; results.out = fn();` + this.#code = `(() => {${script}})();` + this.#vm = new IsolatedVM({ + memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, + timeout: JS_TIMEOUT_MS, + }).withContext(context) } execute() { - const vm = new IsolatedVM({ - memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, - timeout: JS_TIMEOUT_MS, - }).withHelpers() - const result = vm.execute(this.#code) + const result = this.#vm.execute(this.#code) return result } } From 9d335b7fb1c692275494d8836d8839f5dbaec18d Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 8 Feb 2024 00:56:45 +0100 Subject: [PATCH 09/16] Fix perRequestLimit --- packages/server/src/jsRunner/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/src/jsRunner/index.ts b/packages/server/src/jsRunner/index.ts index aab5a64098..20a48916c8 100644 --- a/packages/server/src/jsRunner/index.ts +++ b/packages/server/src/jsRunner/index.ts @@ -16,6 +16,7 @@ export function init() { vm = new IsolatedVM({ memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, timeout: env.JS_PER_EXECUTION_TIME_LIMIT_MS, + perRequestLimit: env.JS_PER_REQUEST_TIME_LIMIT_MS, }).withHelpers() bbCtx.vm = vm From 72c122105f6d0308cd1bfad9491ca850504442d3 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 8 Feb 2024 10:51:42 +0100 Subject: [PATCH 10/16] Clean code --- packages/server/src/jsRunner/vm/index.ts | 50 ++++++++++++++++-------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/packages/server/src/jsRunner/vm/index.ts b/packages/server/src/jsRunner/vm/index.ts index 3cdf05b873..d0d5793dee 100644 --- a/packages/server/src/jsRunner/vm/index.ts +++ b/packages/server/src/jsRunner/vm/index.ts @@ -14,6 +14,35 @@ class ExecutionTimeoutError extends Error { } } +class ModuleHandler { + #modules: { + import: string + moduleKey: string + module: ivm.Module + }[] = [] + + #generateRandomKey = () => `i${crypto.randomUUID().replace(/-/g, "")}` + + registerModule(module: ivm.Module, imports: string) { + this.#modules.push({ + moduleKey: this.#generateRandomKey(), + import: imports, + module: module, + }) + } + + generateImports() { + return this.#modules + .map(m => `import ${m.import} from "${m.moduleKey}"`) + .join(";") + } + + getModule(key: string) { + const module = this.#modules.find(m => m.moduleKey === key) + return module?.module + } +} + export class IsolatedVM implements VM { #isolate: ivm.Isolate #vm: ivm.Context @@ -21,11 +50,7 @@ export class IsolatedVM implements VM { #timeout: number #perRequestLimit?: number - #modules: { - import: string - moduleKey: string - module: ivm.Module - }[] = [] + #moduleHandler = new ModuleHandler() readonly #resultKey = "results" @@ -96,11 +121,7 @@ export class IsolatedVM implements VM { throw new Error(`No imports allowed. Required: ${specifier}`) }) - this.#modules.push({ - import: "helpers", - moduleKey: `i${crypto.randomUUID().replace(/-/g, "")}`, - module: helpersModule, - }) + this.#moduleHandler.registerModule(helpersModule, "helpers") return this } @@ -121,17 +142,14 @@ export class IsolatedVM implements VM { } } - code = [ - ...this.#modules.map(m => `import ${m.import} from "${m.moduleKey}"`), - `results.out=${code};`, - ].join(";") + code = `${this.#moduleHandler.generateImports()};results.out=${code};` const script = this.#isolate.compileModuleSync(code) script.instantiateSync(this.#vm, specifier => { - const module = this.#modules.find(m => m.moduleKey === specifier) + const module = this.#moduleHandler.getModule(specifier) if (module) { - return module.module + return module } throw new Error(`"${specifier}" import not allowed`) From 57952131ac2b4392c5263bea0f75f6e2490fc6cf Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 8 Feb 2024 13:44:07 +0100 Subject: [PATCH 11/16] Replace # for privates --- packages/server/src/jsRunner/vm/index.ts | 94 ++++++++++--------- packages/server/src/utilities/scriptRunner.ts | 10 +- 2 files changed, 53 insertions(+), 51 deletions(-) diff --git a/packages/server/src/jsRunner/vm/index.ts b/packages/server/src/jsRunner/vm/index.ts index d0d5793dee..9bd2b7d162 100644 --- a/packages/server/src/jsRunner/vm/index.ts +++ b/packages/server/src/jsRunner/vm/index.ts @@ -15,44 +15,44 @@ class ExecutionTimeoutError extends Error { } class ModuleHandler { - #modules: { + private modules: { import: string moduleKey: string module: ivm.Module }[] = [] - #generateRandomKey = () => `i${crypto.randomUUID().replace(/-/g, "")}` + private generateRandomKey = () => `i${crypto.randomUUID().replace(/-/g, "")}` registerModule(module: ivm.Module, imports: string) { - this.#modules.push({ - moduleKey: this.#generateRandomKey(), + this.modules.push({ + moduleKey: this.generateRandomKey(), import: imports, module: module, }) } generateImports() { - return this.#modules + return this.modules .map(m => `import ${m.import} from "${m.moduleKey}"`) .join(";") } getModule(key: string) { - const module = this.#modules.find(m => m.moduleKey === key) + const module = this.modules.find(m => m.moduleKey === key) return module?.module } } export class IsolatedVM implements VM { - #isolate: ivm.Isolate - #vm: ivm.Context - #jail: ivm.Reference - #timeout: number - #perRequestLimit?: number + private isolate: ivm.Isolate + private vm: ivm.Context + private jail: ivm.Reference + private timeout: number + private perRequestLimit?: number - #moduleHandler = new ModuleHandler() + private moduleHandler = new ModuleHandler() - readonly #resultKey = "results" + private readonly resultKey = "results" constructor({ memoryLimit, @@ -63,30 +63,30 @@ export class IsolatedVM implements VM { timeout: number perRequestLimit?: number }) { - this.#isolate = new ivm.Isolate({ memoryLimit }) - this.#vm = this.#isolate.createContextSync() - this.#jail = this.#vm.global - this.#jail.setSync("global", this.#jail.derefInto()) + this.isolate = new ivm.Isolate({ memoryLimit }) + this.vm = this.isolate.createContextSync() + this.jail = this.vm.global + this.jail.setSync("global", this.jail.derefInto()) - this.#addToContext({ - [this.#resultKey]: { out: "" }, + this.addToContext({ + [this.resultKey]: { out: "" }, }) - this.#timeout = timeout - this.#perRequestLimit = perRequestLimit + this.timeout = timeout + this.perRequestLimit = perRequestLimit } withHelpers() { - const urlModule = this.#registerCallbacks({ + const urlModule = this.registerCallbacks({ resolve: url.resolve, parse: url.parse, }) - const querystringModule = this.#registerCallbacks({ + const querystringModule = this.registerCallbacks({ escape: querystring.escape, }) - this.#addToContext({ + this.addToContext({ helpersStripProtocol: new ivm.Callback((str: string) => { var parsed = url.parse(str) as any parsed.protocol = "" @@ -101,19 +101,19 @@ export class IsolatedVM implements VM { } }` const helpersSource = loadBundle(BundleType.HELPERS) - const helpersModule = this.#isolate.compileModuleSync( + const helpersModule = this.isolate.compileModuleSync( `${injectedRequire};${helpersSource}` ) - helpersModule.instantiateSync(this.#vm, specifier => { + helpersModule.instantiateSync(this.vm, specifier => { if (specifier === "crypto") { - const cryptoModule = this.#registerCallbacks({ + const cryptoModule = this.registerCallbacks({ randomUUID: crypto.randomUUID, }) - const module = this.#isolate.compileModuleSync( + const module = this.isolate.compileModuleSync( `export default ${cryptoModule}` ) - module.instantiateSync(this.#vm, specifier => { + module.instantiateSync(this.vm, specifier => { throw new Error(`No imports allowed. Required: ${specifier}`) }) return module @@ -121,20 +121,21 @@ export class IsolatedVM implements VM { throw new Error(`No imports allowed. Required: ${specifier}`) }) - this.#moduleHandler.registerModule(helpersModule, "helpers") + this.moduleHandler.registerModule(helpersModule, "helpers") return this } withContext(context: Record) { - this.#addToContext(context) + debugger + this.addToContext(context) return this } execute(code: string): string { - const perRequestLimit = this.#perRequestLimit + const perRequestLimit = this.perRequestLimit if (perRequestLimit) { - const cpuMs = Number(this.#isolate.cpuTime) / 1e6 + const cpuMs = Number(this.isolate.cpuTime) / 1e6 if (cpuMs > perRequestLimit) { throw new ExecutionTimeoutError( `CPU time limit exceeded (${cpuMs}ms > ${perRequestLimit}ms)` @@ -142,12 +143,12 @@ export class IsolatedVM implements VM { } } - code = `${this.#moduleHandler.generateImports()};results.out=${code};` + code = `${this.moduleHandler.generateImports()};results.out=${code};` - const script = this.#isolate.compileModuleSync(code) + const script = this.isolate.compileModuleSync(code) - script.instantiateSync(this.#vm, specifier => { - const module = this.#moduleHandler.getModule(specifier) + script.instantiateSync(this.vm, specifier => { + const module = this.moduleHandler.getModule(specifier) if (module) { return module } @@ -155,13 +156,13 @@ export class IsolatedVM implements VM { throw new Error(`"${specifier}" import not allowed`) }) - script.evaluateSync({ timeout: this.#timeout }) + script.evaluateSync({ timeout: this.timeout }) - const result = this.#getResult() + const result = this.getResult() return result } - #registerCallbacks(functions: Record) { + private registerCallbacks(functions: Record) { const libId = crypto.randomUUID().replace(/-/g, "") const x: Record = {} @@ -169,7 +170,7 @@ export class IsolatedVM implements VM { const key = `f${libId}${funcName}cb` x[funcName] = key - this.#addToContext({ + this.addToContext({ [key]: new ivm.Callback((...params: any[]) => (func as any)(...params)), }) } @@ -183,17 +184,18 @@ export class IsolatedVM implements VM { return mod } - #addToContext(context: Record) { + private addToContext(context: Record) { for (let key in context) { - this.#jail.setSync( + this.jail.setSync( key, - new ivm.ExternalCopy(context[key]).copyInto({ release: true }) + context[key] + //new ivm.ExternalCopy(context[key]).copyInto({ release: true }) ) } } - #getResult() { - const ref = this.#vm.global.getSync(this.#resultKey, { reference: true }) + private getResult() { + const ref = this.vm.global.getSync(this.resultKey, { reference: true }) const result = ref.copySync() ref.release() return result.out diff --git a/packages/server/src/utilities/scriptRunner.ts b/packages/server/src/utilities/scriptRunner.ts index 597c4269ac..2f82fbd49a 100644 --- a/packages/server/src/utilities/scriptRunner.ts +++ b/packages/server/src/utilities/scriptRunner.ts @@ -3,19 +3,19 @@ import { IsolatedVM } from "../jsRunner/vm" const JS_TIMEOUT_MS = 1000 class ScriptRunner { - #code - #vm + private code + private vm constructor(script: string, context: any) { - this.#code = `(() => {${script}})();` - this.#vm = new IsolatedVM({ + this.code = `(() => {${script}})();` + this.vm = new IsolatedVM({ memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, timeout: JS_TIMEOUT_MS, }).withContext(context) } execute() { - const result = this.#vm.execute(this.#code) + const result = this.vm.execute(this.code) return result } } From 2ffe3d71531b254c8954e19bde28f475fc4a46dc Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 8 Feb 2024 13:47:27 +0100 Subject: [PATCH 12/16] Remove debugger --- packages/server/src/jsRunner/vm/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/server/src/jsRunner/vm/index.ts b/packages/server/src/jsRunner/vm/index.ts index 9bd2b7d162..673bb767af 100644 --- a/packages/server/src/jsRunner/vm/index.ts +++ b/packages/server/src/jsRunner/vm/index.ts @@ -126,7 +126,6 @@ export class IsolatedVM implements VM { } withContext(context: Record) { - debugger this.addToContext(context) return this } From c57ccbc04641dc7b3f8b61dd5b083fba276471b7 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 8 Feb 2024 13:51:36 +0100 Subject: [PATCH 13/16] Fix adding to context --- packages/server/src/jsRunner/index.ts | 7 ++++++- packages/server/src/jsRunner/vm/index.ts | 6 ++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/server/src/jsRunner/index.ts b/packages/server/src/jsRunner/index.ts index 20a48916c8..67d5111ea6 100644 --- a/packages/server/src/jsRunner/index.ts +++ b/packages/server/src/jsRunner/index.ts @@ -13,11 +13,16 @@ export function init() { let { vm } = bbCtx if (!vm) { + // Can't copy the native helpers into the isolate. We just ignore them as they are handled properly from the helpersSource + const { helpers, ...ctxToPass } = ctx + vm = new IsolatedVM({ memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, timeout: env.JS_PER_EXECUTION_TIME_LIMIT_MS, perRequestLimit: env.JS_PER_REQUEST_TIME_LIMIT_MS, - }).withHelpers() + }) + .withContext(ctxToPass) + .withHelpers() bbCtx.vm = vm } diff --git a/packages/server/src/jsRunner/vm/index.ts b/packages/server/src/jsRunner/vm/index.ts index 673bb767af..3e33abf99e 100644 --- a/packages/server/src/jsRunner/vm/index.ts +++ b/packages/server/src/jsRunner/vm/index.ts @@ -185,10 +185,12 @@ export class IsolatedVM implements VM { private addToContext(context: Record) { for (let key in context) { + const value = context[key] this.jail.setSync( key, - context[key] - //new ivm.ExternalCopy(context[key]).copyInto({ release: true }) + typeof value === "function" + ? value + : new ivm.ExternalCopy(value).copyInto({ release: true }) ) } } From 1e101744de9f167aae2f177ba774492ccce2edb1 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 8 Feb 2024 16:20:33 +0100 Subject: [PATCH 14/16] Make timeout per invocation more specific --- packages/server/src/api/routes/tests/row.spec.ts | 4 ++-- packages/server/src/environment.ts | 4 ++-- packages/server/src/jsRunner/index.ts | 2 +- packages/server/src/jsRunner/vm/index.ts | 10 +++++----- packages/server/src/utilities/scriptRunner.ts | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 637033c1d0..9bd7b68917 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -2031,7 +2031,7 @@ describe.each([ describe("Formula JS protection", () => { it("should time out JS execution if a single cell takes too long", async () => { - await config.withEnv({ JS_PER_EXECUTION_TIME_LIMIT_MS: 20 }, async () => { + await config.withEnv({ JS_PER_INVOCATION_TIMEOUT_MS: 20 }, async () => { const js = Buffer.from( ` let i = 0; @@ -2071,7 +2071,7 @@ describe.each([ it("should time out JS execution if a multiple cells take too long", async () => { await config.withEnv( { - JS_PER_EXECUTION_TIME_LIMIT_MS: 20, + JS_PER_INVOCATION_TIMEOUT_MS: 20, JS_PER_REQUEST_TIME_LIMIT_MS: 40, }, async () => { diff --git a/packages/server/src/environment.ts b/packages/server/src/environment.ts index 0e94b65df8..7c3e8751c5 100644 --- a/packages/server/src/environment.ts +++ b/packages/server/src/environment.ts @@ -71,8 +71,8 @@ const environment = { SELF_HOSTED: process.env.SELF_HOSTED, HTTP_MB_LIMIT: process.env.HTTP_MB_LIMIT, FORKED_PROCESS_NAME: process.env.FORKED_PROCESS_NAME || "main", - JS_PER_EXECUTION_TIME_LIMIT_MS: - parseIntSafe(process.env.JS_PER_EXECUTION_TIME_LIMIT_MS) || 1000, + JS_PER_INVOCATION_TIMEOUT_MS: + parseIntSafe(process.env.JS_PER_INVOCATION_TIMEOUT_MS) || 1000, JS_PER_REQUEST_TIME_LIMIT_MS: parseIntSafe( process.env.JS_PER_REQUEST_TIME_LIMIT_MS ), diff --git a/packages/server/src/jsRunner/index.ts b/packages/server/src/jsRunner/index.ts index 67d5111ea6..60310585f5 100644 --- a/packages/server/src/jsRunner/index.ts +++ b/packages/server/src/jsRunner/index.ts @@ -18,7 +18,7 @@ export function init() { vm = new IsolatedVM({ memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, - timeout: env.JS_PER_EXECUTION_TIME_LIMIT_MS, + invocationTimeout: env.JS_PER_INVOCATION_TIMEOUT_MS, perRequestLimit: env.JS_PER_REQUEST_TIME_LIMIT_MS, }) .withContext(ctxToPass) diff --git a/packages/server/src/jsRunner/vm/index.ts b/packages/server/src/jsRunner/vm/index.ts index 3e33abf99e..e8422e8f5d 100644 --- a/packages/server/src/jsRunner/vm/index.ts +++ b/packages/server/src/jsRunner/vm/index.ts @@ -47,7 +47,7 @@ export class IsolatedVM implements VM { private isolate: ivm.Isolate private vm: ivm.Context private jail: ivm.Reference - private timeout: number + private invocationTimeout: number private perRequestLimit?: number private moduleHandler = new ModuleHandler() @@ -56,11 +56,11 @@ export class IsolatedVM implements VM { constructor({ memoryLimit, - timeout, + invocationTimeout, perRequestLimit, }: { memoryLimit: number - timeout: number + invocationTimeout: number perRequestLimit?: number }) { this.isolate = new ivm.Isolate({ memoryLimit }) @@ -72,7 +72,7 @@ export class IsolatedVM implements VM { [this.resultKey]: { out: "" }, }) - this.timeout = timeout + this.invocationTimeout = invocationTimeout this.perRequestLimit = perRequestLimit } @@ -155,7 +155,7 @@ export class IsolatedVM implements VM { throw new Error(`"${specifier}" import not allowed`) }) - script.evaluateSync({ timeout: this.timeout }) + script.evaluateSync({ timeout: this.invocationTimeout }) const result = this.getResult() return result diff --git a/packages/server/src/utilities/scriptRunner.ts b/packages/server/src/utilities/scriptRunner.ts index 2f82fbd49a..fa0de87f69 100644 --- a/packages/server/src/utilities/scriptRunner.ts +++ b/packages/server/src/utilities/scriptRunner.ts @@ -10,7 +10,7 @@ class ScriptRunner { this.code = `(() => {${script}})();` this.vm = new IsolatedVM({ memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, - timeout: JS_TIMEOUT_MS, + invocationTimeout: JS_TIMEOUT_MS, }).withContext(context) } From 5bc9eb884a43c4606b58d62a7cfa8e940287f301 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 8 Feb 2024 16:23:27 +0100 Subject: [PATCH 15/16] Make timeout per request more specific --- packages/server/src/api/routes/tests/row.spec.ts | 2 +- packages/server/src/environment.ts | 4 ++-- packages/server/src/jsRunner/index.ts | 2 +- packages/server/src/jsRunner/vm/index.ts | 16 +++++++--------- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 9bd7b68917..10dac3c0ea 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -2072,7 +2072,7 @@ describe.each([ await config.withEnv( { JS_PER_INVOCATION_TIMEOUT_MS: 20, - JS_PER_REQUEST_TIME_LIMIT_MS: 40, + JS_PER_REQUEST_TIMEOUT_MS: 40, }, async () => { const js = Buffer.from( diff --git a/packages/server/src/environment.ts b/packages/server/src/environment.ts index 7c3e8751c5..b5d468ec00 100644 --- a/packages/server/src/environment.ts +++ b/packages/server/src/environment.ts @@ -73,8 +73,8 @@ const environment = { FORKED_PROCESS_NAME: process.env.FORKED_PROCESS_NAME || "main", JS_PER_INVOCATION_TIMEOUT_MS: parseIntSafe(process.env.JS_PER_INVOCATION_TIMEOUT_MS) || 1000, - JS_PER_REQUEST_TIME_LIMIT_MS: parseIntSafe( - process.env.JS_PER_REQUEST_TIME_LIMIT_MS + JS_PER_REQUEST_TIMEOUT_MS: parseIntSafe( + process.env.JS_PER_REQUEST_TIMEOUT_MS ), // old CLIENT_ID: process.env.CLIENT_ID, diff --git a/packages/server/src/jsRunner/index.ts b/packages/server/src/jsRunner/index.ts index 60310585f5..90cc0e2564 100644 --- a/packages/server/src/jsRunner/index.ts +++ b/packages/server/src/jsRunner/index.ts @@ -19,7 +19,7 @@ export function init() { vm = new IsolatedVM({ memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, invocationTimeout: env.JS_PER_INVOCATION_TIMEOUT_MS, - perRequestLimit: env.JS_PER_REQUEST_TIME_LIMIT_MS, + isolateAccumulatedTimeout: env.JS_PER_REQUEST_TIMEOUT_MS, }) .withContext(ctxToPass) .withHelpers() diff --git a/packages/server/src/jsRunner/vm/index.ts b/packages/server/src/jsRunner/vm/index.ts index e8422e8f5d..865aca0454 100644 --- a/packages/server/src/jsRunner/vm/index.ts +++ b/packages/server/src/jsRunner/vm/index.ts @@ -48,7 +48,7 @@ export class IsolatedVM implements VM { private vm: ivm.Context private jail: ivm.Reference private invocationTimeout: number - private perRequestLimit?: number + private isolateAccumulatedTimeout?: number private moduleHandler = new ModuleHandler() @@ -57,11 +57,11 @@ export class IsolatedVM implements VM { constructor({ memoryLimit, invocationTimeout, - perRequestLimit, + isolateAccumulatedTimeout, }: { memoryLimit: number invocationTimeout: number - perRequestLimit?: number + isolateAccumulatedTimeout?: number }) { this.isolate = new ivm.Isolate({ memoryLimit }) this.vm = this.isolate.createContextSync() @@ -73,7 +73,7 @@ export class IsolatedVM implements VM { }) this.invocationTimeout = invocationTimeout - this.perRequestLimit = perRequestLimit + this.isolateAccumulatedTimeout = isolateAccumulatedTimeout } withHelpers() { @@ -131,13 +131,11 @@ export class IsolatedVM implements VM { } execute(code: string): string { - const perRequestLimit = this.perRequestLimit - - if (perRequestLimit) { + if (this.isolateAccumulatedTimeout) { const cpuMs = Number(this.isolate.cpuTime) / 1e6 - if (cpuMs > perRequestLimit) { + if (cpuMs > this.isolateAccumulatedTimeout) { throw new ExecutionTimeoutError( - `CPU time limit exceeded (${cpuMs}ms > ${perRequestLimit}ms)` + `CPU time limit exceeded (${cpuMs}ms > ${this.isolateAccumulatedTimeout}ms)` ) } } From 411dd55259b5af6f2776c877b30ff0493d4497ce Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 8 Feb 2024 16:27:48 +0100 Subject: [PATCH 16/16] Update types --- packages/server/src/jsRunner/vm/index.ts | 2 +- packages/types/src/sdk/vm.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/jsRunner/vm/index.ts b/packages/server/src/jsRunner/vm/index.ts index 865aca0454..fc2c841f6e 100644 --- a/packages/server/src/jsRunner/vm/index.ts +++ b/packages/server/src/jsRunner/vm/index.ts @@ -130,7 +130,7 @@ export class IsolatedVM implements VM { return this } - execute(code: string): string { + execute(code: string): any { if (this.isolateAccumulatedTimeout) { const cpuMs = Number(this.isolate.cpuTime) / 1e6 if (cpuMs > this.isolateAccumulatedTimeout) { diff --git a/packages/types/src/sdk/vm.ts b/packages/types/src/sdk/vm.ts index 3abec4d39d..43b7775d3b 100644 --- a/packages/types/src/sdk/vm.ts +++ b/packages/types/src/sdk/vm.ts @@ -1,3 +1,3 @@ export interface VM { - execute(code: string): string + execute(code: string): any }