work in progress: replace vm2 and vm with isolated-vm

This commit is contained in:
Sam Rose 2024-01-05 13:48:20 +00:00
parent 3e6848fda5
commit 58abca62de
No known key found for this signature in database
10 changed files with 187 additions and 778 deletions

View File

@ -124,6 +124,8 @@ HEALTHCHECK --interval=15s --timeout=15s --start-period=45s CMD "/healthcheck.sh
# must set this just before running
ENV NODE_ENV=production
# this is required for isolated-vm to work on Node 20+
ENV NODE_OPTIONS="--no-node-snapshot"
WORKDIR /
CMD ["./runner.sh"]

View File

@ -82,6 +82,8 @@ EXPOSE 4001
# due to this causing yarn to stop installing dev dependencies
# which are actually needed to get this environment up and running
ENV NODE_ENV=production
# this is required for isolated-vm to work on Node 20+
ENV NODE_OPTIONS="--no-node-snapshot"
ENV CLUSTER_MODE=${CLUSTER_MODE}
ENV TOP_LEVEL_PATH=/app

View File

@ -9,5 +9,5 @@
],
"ext": "js,ts,json",
"ignore": ["src/**/*.spec.ts", "src/**/*.spec.js", "../*/dist/**/*"],
"exec": "yarn build && node ./dist/index.js"
"exec": "yarn build && node --no-node-snapshot ./dist/index.js"
}

View File

@ -72,6 +72,7 @@
"google-auth-library": "7.12.0",
"google-spreadsheet": "3.2.0",
"ioredis": "5.3.2",
"isolated-vm": "^4.6.0",
"jimp": "0.16.1",
"joi": "17.6.0",
"js-yaml": "4.1.0",
@ -106,7 +107,6 @@
"to-json-schema": "0.2.5",
"uuid": "3.3.2",
"validate.js": "0.13.1",
"vm2": "^3.9.19",
"worker-farm": "1.7.0",
"xml2js": "0.5.0"
},

View File

@ -4,11 +4,12 @@ set -e
if [[ -n $CI ]]
then
# Running in ci, where resources are limited
export NODE_OPTIONS="--max-old-space-size=4096"
export NODE_OPTIONS="--max-old-space-size=4096 --no-node-snapshot"
echo "jest --coverage --maxWorkers=2 --forceExit --workerIdleMemoryLimit=2000MB --bail"
jest --coverage --maxWorkers=2 --forceExit --workerIdleMemoryLimit=2000MB --bail
else
# --maxWorkers performs better in development
export NODE_OPTIONS="--no-node-snapshot"
echo "jest --coverage --maxWorkers=2 --forceExit"
jest --coverage --maxWorkers=2 --forceExit
fi

View File

@ -1,4 +1,5 @@
import vm from "vm"
import ivm from "isolated-vm"
import env from "./environment"
import { setJSRunner } from "@budibase/string-templates"
import { context, timers } from "@budibase/backend-core"
@ -31,18 +32,31 @@ export function init() {
}
}
ctx = {
...ctx,
alert: undefined,
setInterval: undefined,
setTimeout: undefined,
const isolate = new ivm.Isolate({ memoryLimit: 64 })
const vm = isolate.createContextSync()
const jail = vm.global
jail.setSync("global", jail.derefInto())
for (let key in ctx) {
let value
if (["alert", "setInterval", "setTimeout"].includes(key)) {
value = undefined
} else {
value = ctx[key]
}
vm.createContext(ctx)
return track(() =>
vm.runInNewContext(js, ctx, {
jail.setSync(
key,
new ivm.ExternalCopy(value).copyInto({ release: true })
)
}
const script = isolate.compileScriptSync(js)
return track(() => {
return script.runSync(vm, {
timeout: env.JS_PER_EXECUTION_TIME_LIMIT_MS,
})
)
})
})
})
}

View File

@ -1,29 +1,64 @@
import fetch from "node-fetch"
import { VM, VMScript } from "vm2"
import ivm, { Context, Script } from "isolated-vm"
const JS_TIMEOUT_MS = 1000
class ScriptRunner {
vm: VM
results: { out: string }
script: VMScript
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)
this.vm = new IsolatedVM({ memoryLimit: 8 })
this.vm.context = {
data: context.data,
params: context.params,
results: { out: "" },
}
this.vm.code = code
}
execute() {
this.vm.run(this.script)
return this.results.out
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()
return result
}
set context(context: Record<string, any>) {
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<Object> {
return new ivm.ExternalCopy(value).copyInto({ release: true })
}
}
export default ScriptRunner

View File

@ -44,6 +44,8 @@ EXPOSE 4001
# due to this causing yarn to stop installing dev dependencies
# which are actually needed to get this environment up and running
ENV NODE_ENV=production
# this is required for isolated-vm to work on Node 20+
ENV NODE_OPTIONS="--no-node-snapshot"
ENV CLUSTER_MODE=${CLUSTER_MODE}
ENV SERVICE=worker-service
ENV POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU

View File

@ -9,5 +9,5 @@
],
"ext": "js,ts,json",
"ignore": ["src/**/*.spec.ts", "src/**/*.spec.js", "../*/dist/**/*"],
"exec": "yarn build && node dist/index.js"
"exec": "yarn build && node --no-node-snapshot dist/index.js"
}

853
yarn.lock

File diff suppressed because it is too large Load Diff