Keep isolateRefs in context

This commit is contained in:
Adria Navarro 2024-01-24 14:02:34 +01:00
parent d7b5aa08db
commit bc7825dc93
2 changed files with 70 additions and 56 deletions

View File

@ -1,5 +1,5 @@
import { IdentityContext } from "@budibase/types" import { IdentityContext } from "@budibase/types"
import { Isolate, Context } from "isolated-vm" import { Isolate, Context, Module } from "isolated-vm"
// keep this out of Budibase types, don't want to expose context info // keep this out of Budibase types, don't want to expose context info
export type ContextMap = { export type ContextMap = {
@ -10,6 +10,9 @@ export type ContextMap = {
isScim?: boolean isScim?: boolean
automationId?: string automationId?: string
isMigrating?: boolean isMigrating?: boolean
jsIsolate?: Isolate isolateRefs?: {
jsContext?: Context jsIsolate: Isolate
jsContext: Context
helpersModule: Module
}
} }

View File

@ -7,18 +7,22 @@ import fs from "fs"
import url from "url" import url from "url"
import crypto from "crypto" import crypto from "crypto"
const helpersSource = fs.readFileSync(
`${require.resolve("@budibase/string-templates/index-helpers")}`,
"utf8"
)
export function init() { export function init() {
const helpersSource = fs.readFileSync(
`${require.resolve("@budibase/string-templates/index-helpers")}`,
"utf8"
)
setJSRunner((js: string, ctx: Record<string, any>) => { setJSRunner((js: string, ctx: Record<string, any>) => {
return tracer.trace("runJS", {}, span => { return tracer.trace("runJS", {}, span => {
const bbCtx = context.getCurrentContext() || {} const bbCtx = context.getCurrentContext()!
let { jsIsolate = new ivm.Isolate({ memoryLimit: 64 }) } = bbCtx
let { jsContext = jsIsolate.createContextSync() } = bbCtx
const injectedRequire = `const require = function(val){ const isolateRefs = bbCtx.isolateRefs
if (!isolateRefs) {
const jsIsolate = new ivm.Isolate({ memoryLimit: 64 })
const jsContext = jsIsolate.createContextSync()
const injectedRequire = `const require = function(val){
switch (val) { switch (val) {
case "url": case "url":
return { return {
@ -28,63 +32,70 @@ export function init() {
} }
};` };`
const global = jsContext.global const global = jsContext.global
global.setSync( global.setSync(
"urlResolveCb", "urlResolveCb",
new ivm.Callback((...params: Parameters<typeof url.resolve>) => new ivm.Callback((...params: Parameters<typeof url.resolve>) =>
url.resolve(...params) url.resolve(...params)
)
) )
)
global.setSync( global.setSync(
"urlParseCb", "urlParseCb",
new ivm.Callback((...params: Parameters<typeof url.parse>) => new ivm.Callback((...params: Parameters<typeof url.parse>) =>
url.parse(...params) url.parse(...params)
)
) )
)
const helpersModule = jsIsolate.compileModuleSync( const helpersModule = jsIsolate.compileModuleSync(
`${injectedRequire};${helpersSource}` `${injectedRequire};${helpersSource}`
) )
const cryptoModule = jsIsolate.compileModuleSync(`export default { const cryptoModule = jsIsolate.compileModuleSync(`export default {
randomUUID: cryptoRandomUUIDCb, randomUUID: cryptoRandomUUIDCb,
}`) }`)
cryptoModule.instantiateSync(jsContext, specifier => { cryptoModule.instantiateSync(jsContext, specifier => {
throw new Error(`No imports allowed. Required: ${specifier}`) throw new Error(`No imports allowed. Required: ${specifier}`)
})
global.setSync(
"cryptoRandomUUIDCb",
new ivm.Callback((...params: Parameters<typeof crypto.randomUUID>) => {
return crypto.randomUUID(...params)
}) })
)
helpersModule.instantiateSync(jsContext, specifier => { global.setSync(
if (specifier === "crypto") { "cryptoRandomUUIDCb",
return cryptoModule new ivm.Callback(
} (...params: Parameters<typeof crypto.randomUUID>) => {
throw new Error(`No imports allowed. Required: ${specifier}`) return crypto.randomUUID(...params)
}) }
const perRequestLimit = env.JS_PER_REQUEST_TIME_LIMIT_MS
if (perRequestLimit) {
const cpuMs = Number(jsIsolate.cpuTime) / 1e6
if (cpuMs > perRequestLimit) {
throw new Error(
`CPU time limit exceeded (${cpuMs}ms > ${perRequestLimit}ms)`
) )
)
helpersModule.instantiateSync(jsContext, specifier => {
if (specifier === "crypto") {
return cryptoModule
}
throw new Error(`No imports allowed. Required: ${specifier}`)
})
const perRequestLimit = env.JS_PER_REQUEST_TIME_LIMIT_MS
if (perRequestLimit) {
const cpuMs = Number(jsIsolate.cpuTime) / 1e6
if (cpuMs > perRequestLimit) {
throw new Error(
`CPU time limit exceeded (${cpuMs}ms > ${perRequestLimit}ms)`
)
}
} }
for (const [key, value] of Object.entries(ctx)) {
if (key === "helpers") {
// Can't copy the native helpers into the isolate. We just ignore them as they are handled properly from the helpersSource
continue
}
global.setSync(key, value)
}
bbCtx.isolateRefs = { jsContext, jsIsolate, helpersModule }
} }
for (const [key, value] of Object.entries(ctx)) { let { jsIsolate, jsContext, helpersModule } = bbCtx.isolateRefs!
if (key === "helpers") {
// Can't copy the native helpers into the isolate. We just ignore them as they are handled properly from the helpersSource
continue
}
global.setSync(key, value)
}
const script = jsIsolate.compileModuleSync( const script = jsIsolate.compileModuleSync(
`import helpers from "compiled_module";${js};cb(run());`, `import helpers from "compiled_module";${js};cb(run());`,
@ -100,7 +111,7 @@ export function init() {
}) })
let result let result
global.setSync( jsContext.global.setSync(
"cb", "cb",
new ivm.Callback((value: any) => { new ivm.Callback((value: any) => {
result = value result = value