1
0
Fork 0
mirror of synced 2024-07-06 15:00:49 +12: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 { setJSRunner, setOnErrorLog } from "@budibase/string-templates"
import { context, logging, timers } from "@budibase/backend-core"
import { setJSRunner, JsErrorTimeout } from "@budibase/string-templates"
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() {
setJSRunner((js: string, ctx: vm.Context) => {
setJSRunner((js: string, ctx: Record<string, any>) => {
return tracer.trace("runJS", {}, span => {
const perRequestLimit = env.JS_PER_REQUEST_TIMEOUT_MS
let track: TrackerFn = f => f()
if (perRequestLimit) {
const bbCtx = tracer.trace("runJS.getCurrentContext", {}, span =>
context.getCurrentContext()
)
if (bbCtx) {
if (!bbCtx.jsExecutionTracker) {
span?.addTags({
createdExecutionTracker: true,
})
bbCtx.jsExecutionTracker = tracer.trace(
"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
)
try {
const bbCtx = context.getCurrentContext()!
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,
invocationTimeout: env.JS_PER_INVOCATION_TIMEOUT_MS,
isolateAccumulatedTimeout: env.JS_PER_REQUEST_TIMEOUT_MS,
})
.withContext(ctxToPass)
.withHelpers()
bbCtx.vm = vm
}
}
ctx = {
...ctx,
alert: undefined,
setInterval: undefined,
setTimeout: undefined,
}
const result = vm.execute(js)
vm.createContext(ctx)
return track(() =>
vm.runInNewContext(js, ctx, {
timeout: env.JS_PER_INVOCATION_TIMEOUT_MS,
})
)
return result
} catch (error: any) {
if (error.message === "Script execution timed out.") {
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
if (transformer) {
const runner = new ScriptRunner(transformer, {
data: rows,
params: enrichedParameters,
})
const runner = new ScriptRunner(
transformer,
{
data: rows,
params: enrichedParameters,
},
{
parseBson: datasource.source === SourceName.MONGODB,
}
)
rows = runner.execute()
}

View file

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