work in progress: replace vm2 and vm with isolated-vm
This commit is contained in:
parent
3e6848fda5
commit
58abca62de
|
@ -124,6 +124,8 @@ HEALTHCHECK --interval=15s --timeout=15s --start-period=45s CMD "/healthcheck.sh
|
||||||
|
|
||||||
# must set this just before running
|
# must set this just before running
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
# this is required for isolated-vm to work on Node 20+
|
||||||
|
ENV NODE_OPTIONS="--no-node-snapshot"
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
|
|
||||||
CMD ["./runner.sh"]
|
CMD ["./runner.sh"]
|
||||||
|
|
|
@ -82,6 +82,8 @@ EXPOSE 4001
|
||||||
# due to this causing yarn to stop installing dev dependencies
|
# due to this causing yarn to stop installing dev dependencies
|
||||||
# which are actually needed to get this environment up and running
|
# which are actually needed to get this environment up and running
|
||||||
ENV NODE_ENV=production
|
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 CLUSTER_MODE=${CLUSTER_MODE}
|
||||||
ENV TOP_LEVEL_PATH=/app
|
ENV TOP_LEVEL_PATH=/app
|
||||||
|
|
||||||
|
|
|
@ -9,5 +9,5 @@
|
||||||
],
|
],
|
||||||
"ext": "js,ts,json",
|
"ext": "js,ts,json",
|
||||||
"ignore": ["src/**/*.spec.ts", "src/**/*.spec.js", "../*/dist/**/*"],
|
"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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,7 @@
|
||||||
"google-auth-library": "7.12.0",
|
"google-auth-library": "7.12.0",
|
||||||
"google-spreadsheet": "3.2.0",
|
"google-spreadsheet": "3.2.0",
|
||||||
"ioredis": "5.3.2",
|
"ioredis": "5.3.2",
|
||||||
|
"isolated-vm": "^4.6.0",
|
||||||
"jimp": "0.16.1",
|
"jimp": "0.16.1",
|
||||||
"joi": "17.6.0",
|
"joi": "17.6.0",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
|
@ -106,7 +107,6 @@
|
||||||
"to-json-schema": "0.2.5",
|
"to-json-schema": "0.2.5",
|
||||||
"uuid": "3.3.2",
|
"uuid": "3.3.2",
|
||||||
"validate.js": "0.13.1",
|
"validate.js": "0.13.1",
|
||||||
"vm2": "^3.9.19",
|
|
||||||
"worker-farm": "1.7.0",
|
"worker-farm": "1.7.0",
|
||||||
"xml2js": "0.5.0"
|
"xml2js": "0.5.0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,11 +4,12 @@ set -e
|
||||||
if [[ -n $CI ]]
|
if [[ -n $CI ]]
|
||||||
then
|
then
|
||||||
# Running in ci, where resources are limited
|
# 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"
|
echo "jest --coverage --maxWorkers=2 --forceExit --workerIdleMemoryLimit=2000MB --bail"
|
||||||
jest --coverage --maxWorkers=2 --forceExit --workerIdleMemoryLimit=2000MB --bail
|
jest --coverage --maxWorkers=2 --forceExit --workerIdleMemoryLimit=2000MB --bail
|
||||||
else
|
else
|
||||||
# --maxWorkers performs better in development
|
# --maxWorkers performs better in development
|
||||||
|
export NODE_OPTIONS="--no-node-snapshot"
|
||||||
echo "jest --coverage --maxWorkers=2 --forceExit"
|
echo "jest --coverage --maxWorkers=2 --forceExit"
|
||||||
jest --coverage --maxWorkers=2 --forceExit
|
jest --coverage --maxWorkers=2 --forceExit
|
||||||
fi
|
fi
|
|
@ -1,4 +1,5 @@
|
||||||
import vm from "vm"
|
import vm from "vm"
|
||||||
|
import ivm from "isolated-vm"
|
||||||
import env from "./environment"
|
import env from "./environment"
|
||||||
import { setJSRunner } from "@budibase/string-templates"
|
import { setJSRunner } from "@budibase/string-templates"
|
||||||
import { context, timers } from "@budibase/backend-core"
|
import { context, timers } from "@budibase/backend-core"
|
||||||
|
@ -31,18 +32,31 @@ export function init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = {
|
const isolate = new ivm.Isolate({ memoryLimit: 64 })
|
||||||
...ctx,
|
const vm = isolate.createContextSync()
|
||||||
alert: undefined,
|
const jail = vm.global
|
||||||
setInterval: undefined,
|
jail.setSync("global", jail.derefInto())
|
||||||
setTimeout: undefined,
|
|
||||||
|
for (let key in ctx) {
|
||||||
|
let value
|
||||||
|
if (["alert", "setInterval", "setTimeout"].includes(key)) {
|
||||||
|
value = undefined
|
||||||
|
} else {
|
||||||
|
value = ctx[key]
|
||||||
|
}
|
||||||
|
jail.setSync(
|
||||||
|
key,
|
||||||
|
new ivm.ExternalCopy(value).copyInto({ release: true })
|
||||||
|
)
|
||||||
}
|
}
|
||||||
vm.createContext(ctx)
|
|
||||||
return track(() =>
|
const script = isolate.compileScriptSync(js)
|
||||||
vm.runInNewContext(js, ctx, {
|
|
||||||
|
return track(() => {
|
||||||
|
return script.runSync(vm, {
|
||||||
timeout: env.JS_PER_EXECUTION_TIME_LIMIT_MS,
|
timeout: env.JS_PER_EXECUTION_TIME_LIMIT_MS,
|
||||||
})
|
})
|
||||||
)
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,64 @@
|
||||||
import fetch from "node-fetch"
|
import ivm, { Context, Script } from "isolated-vm"
|
||||||
import { VM, VMScript } from "vm2"
|
|
||||||
|
|
||||||
const JS_TIMEOUT_MS = 1000
|
const JS_TIMEOUT_MS = 1000
|
||||||
|
|
||||||
class ScriptRunner {
|
class ScriptRunner {
|
||||||
vm: VM
|
vm: IsolatedVM
|
||||||
results: { out: string }
|
|
||||||
script: VMScript
|
|
||||||
|
|
||||||
constructor(script: string, context: any) {
|
constructor(script: string, context: any) {
|
||||||
const code = `let fn = () => {\n${script}\n}; results.out = fn();`
|
const code = `let fn = () => {\n${script}\n}; results.out = fn();`
|
||||||
this.vm = new VM({
|
this.vm = new IsolatedVM({ memoryLimit: 8 })
|
||||||
timeout: JS_TIMEOUT_MS,
|
this.vm.context = {
|
||||||
})
|
data: context.data,
|
||||||
this.results = { out: "" }
|
params: context.params,
|
||||||
this.vm.setGlobals(context)
|
results: { out: "" },
|
||||||
this.vm.setGlobal("fetch", fetch)
|
}
|
||||||
this.vm.setGlobal("results", this.results)
|
this.vm.code = code
|
||||||
this.script = new VMScript(code)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
execute() {
|
execute() {
|
||||||
this.vm.run(this.script)
|
this.vm.runScript()
|
||||||
return this.results.out
|
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
|
export default ScriptRunner
|
||||||
|
|
|
@ -44,6 +44,8 @@ EXPOSE 4001
|
||||||
# due to this causing yarn to stop installing dev dependencies
|
# due to this causing yarn to stop installing dev dependencies
|
||||||
# which are actually needed to get this environment up and running
|
# which are actually needed to get this environment up and running
|
||||||
ENV NODE_ENV=production
|
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 CLUSTER_MODE=${CLUSTER_MODE}
|
||||||
ENV SERVICE=worker-service
|
ENV SERVICE=worker-service
|
||||||
ENV POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
|
ENV POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
|
||||||
|
|
|
@ -9,5 +9,5 @@
|
||||||
],
|
],
|
||||||
"ext": "js,ts,json",
|
"ext": "js,ts,json",
|
||||||
"ignore": ["src/**/*.spec.ts", "src/**/*.spec.js", "../*/dist/**/*"],
|
"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"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue