1
0
Fork 0
mirror of synced 2024-10-05 12:34:50 +13:00

Use isolated-vm

This commit is contained in:
Adria Navarro 2024-02-12 18:07:17 +01:00
parent 2fe6dbe8a5
commit 6f6100e7a2
3 changed files with 67 additions and 76 deletions

View file

@ -1,68 +1,41 @@
import vm from "vm"
import env from "../environment" import env from "../environment"
import { setJSRunner, setOnErrorLog } from "@budibase/string-templates" import { setJSRunner, JsErrorTimeout } from "@budibase/string-templates"
import { context, logging, timers } from "@budibase/backend-core"
import tracer from "dd-trace" import tracer from "dd-trace"
import { serializeError } from "serialize-error"
type TrackerFn = <T>(f: () => T) => T import { IsolatedVM } from "./vm"
import { context } from "@budibase/backend-core"
export function init() { export function init() {
setJSRunner((js: string, ctx: vm.Context) => { setJSRunner((js: string, ctx: Record<string, any>) => {
return tracer.trace("runJS", {}, span => { return tracer.trace("runJS", {}, span => {
const perRequestLimit = env.JS_PER_REQUEST_TIMEOUT_MS try {
let track: TrackerFn = f => f() const bbCtx = context.getCurrentContext()!
if (perRequestLimit) {
const bbCtx = tracer.trace("runJS.getCurrentContext", {}, span => let { vm } = bbCtx
context.getCurrentContext() if (!vm) {
) // Can't copy the native helpers into the isolate. We just ignore them as they are handled properly from the helpersSource
if (bbCtx) { const { helpers, ...ctxToPass } = ctx
if (!bbCtx.jsExecutionTracker) {
span?.addTags({ vm = new IsolatedVM({
createdExecutionTracker: true, memoryLimit: env.JS_RUNNER_MEMORY_LIMIT,
}) invocationTimeout: env.JS_PER_INVOCATION_TIMEOUT_MS,
bbCtx.jsExecutionTracker = tracer.trace( isolateAccumulatedTimeout: env.JS_PER_REQUEST_TIMEOUT_MS,
"runJS.createExecutionTimeTracker",
{},
span => timers.ExecutionTimeTracker.withLimit(perRequestLimit)
)
}
span?.addTags({
js: {
limitMS: bbCtx.jsExecutionTracker.limitMs,
elapsedMS: bbCtx.jsExecutionTracker.elapsedMS,
},
})
// We call checkLimit() here to prevent paying the cost of creating
// a new VM context below when we don't need to.
tracer.trace("runJS.checkLimitAndBind", {}, span => {
bbCtx.jsExecutionTracker!.checkLimit()
track = bbCtx.jsExecutionTracker!.track.bind(
bbCtx.jsExecutionTracker
)
}) })
.withContext(ctxToPass)
.withHelpers()
bbCtx.vm = vm
} }
}
ctx = { const result = vm.execute(js)
...ctx,
alert: undefined,
setInterval: undefined,
setTimeout: undefined,
}
vm.createContext(ctx) return result
return track(() => } catch (error: any) {
vm.runInNewContext(js, ctx, { if (error.message === "Script execution timed out.") {
timeout: env.JS_PER_INVOCATION_TIMEOUT_MS, throw new JsErrorTimeout()
}) }
) throw error
}
}) })
}) })
if (env.LOG_JS_ERRORS) {
setOnErrorLog((error: Error) => {
logging.logWarn(JSON.stringify(serializeError(error)))
})
}
} }

View file

@ -127,10 +127,16 @@ class QueryRunner {
// transform as required // transform as required
if (transformer) { if (transformer) {
const runner = new ScriptRunner(transformer, { const runner = new ScriptRunner(
data: rows, transformer,
params: enrichedParameters, {
}) data: rows,
params: enrichedParameters,
},
{
parseBson: datasource.source === SourceName.MONGODB,
}
)
rows = runner.execute() rows = runner.execute()
} }

View file

@ -1,28 +1,40 @@
import fetch from "node-fetch" import tracer, { Span } from "dd-trace"
import { VM, VMScript } from "vm2" import env from "../environment"
import { IsolatedVM } from "../jsRunner/vm"
const JS_TIMEOUT_MS = 1000 const JS_TIMEOUT_MS = 1000
class ScriptRunner { class ScriptRunner {
vm: VM private code: string
results: { out: string } private vm: IsolatedVM
script: VMScript
constructor(script: string, context: any) { private tracerSpan: Span
const code = `let fn = () => {\n${script}\n}; results.out = fn();`
this.vm = new VM({ constructor(script: string, context: any, { parseBson = false } = {}) {
timeout: JS_TIMEOUT_MS, this.tracerSpan = tracer.startSpan("scriptRunner", { tags: { parseBson } })
})
this.results = { out: "" } this.code = `(() => {${script}})();`
this.vm.setGlobals(context) this.vm = new IsolatedVM({
this.vm.setGlobal("fetch", fetch) memoryLimit: env.JS_RUNNER_MEMORY_LIMIT,
this.vm.setGlobal("results", this.results) invocationTimeout: JS_TIMEOUT_MS,
this.script = new VMScript(code) }).withContext(context)
if (parseBson && context.data) {
this.vm = this.vm.withParsingBson(context.data)
}
} }
execute() { execute() {
this.vm.run(this.script) const result = tracer.trace(
return this.results.out "scriptRunner.execute",
{ childOf: this.tracerSpan },
() => {
const result = this.vm.execute(this.code)
return result
}
)
this.tracerSpan.finish()
return result
} }
} }