Create wrapper
This commit is contained in:
parent
218ba1d283
commit
c5abb4f846
|
@ -0,0 +1,154 @@
|
|||
import ivm from "isolated-vm"
|
||||
|
||||
import url from "url"
|
||||
import crypto from "crypto"
|
||||
import querystring from "querystring"
|
||||
|
||||
import { BundleType, loadBundle } from "../bundles"
|
||||
import { VM } from "@budibase/types"
|
||||
|
||||
export class IsolatedVM implements VM {
|
||||
#isolate: ivm.Isolate
|
||||
#vm: ivm.Context
|
||||
#jail: ivm.Reference
|
||||
#timeout: number
|
||||
|
||||
#modules: Record<
|
||||
string,
|
||||
{
|
||||
headCode: string
|
||||
module: ivm.Module
|
||||
}
|
||||
> = {}
|
||||
|
||||
readonly #resultKey = "results"
|
||||
|
||||
constructor({
|
||||
memoryLimit,
|
||||
timeout,
|
||||
}: {
|
||||
memoryLimit: number
|
||||
timeout: number
|
||||
}) {
|
||||
this.#isolate = new ivm.Isolate({ memoryLimit })
|
||||
this.#vm = this.#isolate.createContextSync()
|
||||
this.#jail = this.#vm.global
|
||||
this.#jail.setSync("global", this.#jail.derefInto())
|
||||
|
||||
this.#addToContext({
|
||||
[this.#resultKey]: { out: "" },
|
||||
})
|
||||
|
||||
this.#timeout = timeout
|
||||
}
|
||||
|
||||
get cpuTime() {
|
||||
return this.#isolate.cpuTime
|
||||
}
|
||||
|
||||
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 helpersSource = loadBundle(BundleType.HELPERS)
|
||||
const helpersModule = this.#isolate.compileModuleSync(
|
||||
`${injectedRequire};${helpersSource}`
|
||||
)
|
||||
|
||||
this.#addToContext({
|
||||
urlResolveCb: new ivm.Callback(
|
||||
(...params: Parameters<typeof url.resolve>) => url.resolve(...params)
|
||||
),
|
||||
urlParseCb: new ivm.Callback((...params: Parameters<typeof url.parse>) =>
|
||||
url.parse(...params)
|
||||
),
|
||||
querystringEscapeCb: new ivm.Callback(
|
||||
(...params: Parameters<typeof querystring.escape>) =>
|
||||
querystring.escape(...params)
|
||||
),
|
||||
helpersStripProtocol: new ivm.Callback((str: string) => {
|
||||
var parsed = url.parse(str) as any
|
||||
parsed.protocol = ""
|
||||
return parsed.format()
|
||||
}),
|
||||
})
|
||||
|
||||
const cryptoModule = this.#isolate.compileModuleSync(
|
||||
`export default { randomUUID: cryptoRandomUUIDCb }`
|
||||
)
|
||||
cryptoModule.instantiateSync(this.#vm, specifier => {
|
||||
throw new Error(`No imports allowed. Required: ${specifier}`)
|
||||
})
|
||||
|
||||
this.#addToContext({
|
||||
cryptoRandomUUIDCb: new ivm.Callback(
|
||||
(...params: Parameters<typeof crypto.randomUUID>) => {
|
||||
return crypto.randomUUID(...params)
|
||||
}
|
||||
),
|
||||
})
|
||||
|
||||
helpersModule.instantiateSync(this.#vm, specifier => {
|
||||
if (specifier === "crypto") {
|
||||
return cryptoModule
|
||||
}
|
||||
throw new Error(`No imports allowed. Required: ${specifier}`)
|
||||
})
|
||||
|
||||
this.#modules["compiled_module"] = {
|
||||
headCode: 'import helpers from "compiled_module"',
|
||||
module: helpersModule,
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
execute(code: string): string {
|
||||
code = [
|
||||
...Object.values(this.#modules).map(m => m.headCode),
|
||||
`results.out=${code};`,
|
||||
].join(";")
|
||||
|
||||
const script = this.#isolate.compileModuleSync(code)
|
||||
|
||||
script.instantiateSync(this.#vm, specifier => {
|
||||
if (specifier === "compiled_module") {
|
||||
return this.#modules[specifier].module
|
||||
}
|
||||
|
||||
throw new Error(`"${specifier}" import not allowed`)
|
||||
})
|
||||
|
||||
script.evaluateSync({ timeout: this.#timeout })
|
||||
|
||||
const result = this.#getResult()
|
||||
return result
|
||||
}
|
||||
|
||||
#addToContext(context: Record<string, any>) {
|
||||
for (let key in context) {
|
||||
this.#jail.setSync(
|
||||
key,
|
||||
new ivm.ExternalCopy(context[key]).copyInto({ release: true })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#getResult() {
|
||||
const ref = this.#vm.global.getSync(this.#resultKey, { reference: true })
|
||||
const result = ref.copySync()
|
||||
ref.release()
|
||||
return result.out
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue