Use wrapper
This commit is contained in:
parent
c5abb4f846
commit
3b8b60aa03
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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<string, any>) => {
|
||||
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<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 }
|
||||
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) {
|
||||
|
|
Loading…
Reference in New Issue