Use wrapper

This commit is contained in:
Adria Navarro 2024-02-07 18:08:15 +01:00
parent c5abb4f846
commit 3b8b60aa03
2 changed files with 11 additions and 128 deletions

View File

@ -1,5 +1,4 @@
import { IdentityContext, VM } 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 // keep this out of Budibase types, don't want to expose context info
export type ContextMap = { export type ContextMap = {
@ -10,10 +9,5 @@ export type ContextMap = {
isScim?: boolean isScim?: boolean
automationId?: string automationId?: string
isMigrating?: boolean isMigrating?: boolean
isolateRefs?: {
jsIsolate: Isolate
jsContext: Context
helpersModule: Module
}
vm?: VM vm?: VM
} }

View File

@ -1,12 +1,9 @@
import ivm from "isolated-vm"
import env from "../environment" import env from "../environment"
import { setJSRunner, JsErrorTimeout } from "@budibase/string-templates" import { setJSRunner, JsErrorTimeout } from "@budibase/string-templates"
import { context } from "@budibase/backend-core"
import tracer from "dd-trace" import tracer from "dd-trace"
import url from "url"
import crypto from "crypto" import { IsolatedVM } from "./vm"
import querystring from "querystring" import { context } from "@budibase/backend-core"
import { BundleType, loadBundle } from "./bundles"
class ExecutionTimeoutError extends Error { class ExecutionTimeoutError extends Error {
constructor(message: string) { constructor(message: string) {
@ -16,109 +13,24 @@ class ExecutionTimeoutError extends Error {
} }
export function init() { export function init() {
const helpersSource = loadBundle(BundleType.HELPERS)
setJSRunner((js: string, ctx: Record<string, any>) => { setJSRunner((js: string, ctx: Record<string, any>) => {
return tracer.trace("runJS", {}, span => { return tracer.trace("runJS", {}, span => {
try { try {
const bbCtx = context.getCurrentContext()! const bbCtx = context.getCurrentContext()!
const isolateRefs = bbCtx.isolateRefs let { vm } = bbCtx
if (!isolateRefs) { if (!vm) {
const jsIsolate = new ivm.Isolate({ vm = new IsolatedVM({
memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, memoryLimit: env.JS_RUNNER_MEMORY_LIMIT,
}) timeout: env.JS_PER_EXECUTION_TIME_LIMIT_MS,
const jsContext = jsIsolate.createContextSync() }).withHelpers()
const injectedRequire = ` bbCtx.vm = vm
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<typeof url.resolve>) =>
url.resolve(...params)
)
)
global.setSync(
"urlParseCb",
new ivm.Callback((...params: Parameters<typeof url.parse>) =>
url.parse(...params)
)
)
global.setSync(
"querystringEscapeCb",
new ivm.Callback(
(...params: Parameters<typeof querystring.escape>) =>
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<typeof crypto.randomUUID>) => {
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 }
} }
let { jsIsolate, jsContext, helpersModule } = bbCtx.isolateRefs!
const perRequestLimit = env.JS_PER_REQUEST_TIME_LIMIT_MS const perRequestLimit = env.JS_PER_REQUEST_TIME_LIMIT_MS
if (perRequestLimit) { if (perRequestLimit) {
const cpuMs = Number(jsIsolate.cpuTime) / 1e6 const cpuMs = Number(vm.cpuTime) / 1e6
if (cpuMs > perRequestLimit) { if (cpuMs > perRequestLimit) {
throw new ExecutionTimeoutError( throw new ExecutionTimeoutError(
`CPU time limit exceeded (${cpuMs}ms > ${perRequestLimit}ms)` `CPU time limit exceeded (${cpuMs}ms > ${perRequestLimit}ms)`
@ -126,30 +38,7 @@ export function init() {
} }
} }
const script = jsIsolate.compileModuleSync( const result = vm.execute(js)
`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,
})
return result return result
} catch (error: any) { } catch (error: any) {