🔒 Use isolated-vm
This commit is contained in:
@ -4,9 +4,9 @@ import { parseGuessedValueType } from './parseGuessedValueType'
|
||||
import { isDefined } from '@typebot.io/lib'
|
||||
import { safeStringify } from '@typebot.io/lib/safeStringify'
|
||||
import { Variable } from './types'
|
||||
import vm from 'vm'
|
||||
import ivm from 'isolated-vm'
|
||||
|
||||
const defaultTimeout = 10
|
||||
const defaultTimeout = 10 * 1000
|
||||
|
||||
type Props = {
|
||||
variables: Variable[]
|
||||
@ -19,9 +19,9 @@ export const executeFunction = async ({
|
||||
body,
|
||||
args: initialArgs,
|
||||
}: Props) => {
|
||||
const parsedBody = `(async function() {${parseVariables(variables, {
|
||||
const parsedBody = parseVariables(variables, {
|
||||
fieldToParse: 'id',
|
||||
})(body)}})()`
|
||||
})(body)
|
||||
|
||||
const args = (
|
||||
extractVariablesFromText(variables)(body).map((variable) => ({
|
||||
@ -40,21 +40,40 @@ export const executeFunction = async ({
|
||||
updatedVariables[key] = value
|
||||
}
|
||||
|
||||
const context = vm.createContext({
|
||||
...Object.fromEntries(args.map(({ id, value }) => [id, value])),
|
||||
setVariable,
|
||||
fetch,
|
||||
setTimeout,
|
||||
const isolate = new ivm.Isolate()
|
||||
const context = isolate.createContextSync()
|
||||
const jail = context.global
|
||||
jail.setSync('global', jail.derefInto())
|
||||
context.evalClosure(
|
||||
'globalThis.setVariable = (...args) => $0.apply(undefined, args, { arguments: { copy: true }, promise: true, result: { copy: true, promise: true } })',
|
||||
[new ivm.Reference(setVariable)]
|
||||
)
|
||||
context.evalClosure(
|
||||
'globalThis.fetch = (...args) => $0.apply(undefined, args, { arguments: { copy: true }, promise: true, result: { copy: true, promise: true } })',
|
||||
[
|
||||
new ivm.Reference(async (...args: any[]) => {
|
||||
// @ts-ignore
|
||||
const response = await fetch(...args)
|
||||
return response.text()
|
||||
}),
|
||||
]
|
||||
)
|
||||
args.forEach(({ id, value }) => {
|
||||
jail.setSync(id, value)
|
||||
})
|
||||
|
||||
const timeout = new Timeout()
|
||||
const run = (code: string) =>
|
||||
context.evalClosure(
|
||||
`return (async function() {
|
||||
const AsyncFunction = async function () {}.constructor;
|
||||
return new AsyncFunction($0)();
|
||||
}())`,
|
||||
[code],
|
||||
{ result: { copy: true, promise: true }, timeout: defaultTimeout }
|
||||
)
|
||||
|
||||
try {
|
||||
const output: unknown = await timeout.wrap(
|
||||
await vm.runInContext(parsedBody, context),
|
||||
defaultTimeout * 1000
|
||||
)
|
||||
timeout.clear()
|
||||
const output = await run(parsedBody)
|
||||
console.log('Output', output)
|
||||
return {
|
||||
output: safeStringify(output) ?? '',
|
||||
newVariables: Object.entries(updatedVariables)
|
||||
@ -86,33 +105,3 @@ export const executeFunction = async ({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Timeout {
|
||||
private ids: NodeJS.Timeout[]
|
||||
|
||||
constructor() {
|
||||
this.ids = []
|
||||
}
|
||||
|
||||
private set = (delay: number) =>
|
||||
new Promise((_, reject) => {
|
||||
const id = setTimeout(() => {
|
||||
reject(`Script ${defaultTimeout}s timeout reached`)
|
||||
this.clear(id)
|
||||
}, delay)
|
||||
this.ids.push(id)
|
||||
})
|
||||
|
||||
wrap = (promise: Promise<any>, delay: number) =>
|
||||
Promise.race([promise, this.set(delay)])
|
||||
|
||||
clear = (...ids: NodeJS.Timeout[]) => {
|
||||
this.ids = this.ids.filter((id) => {
|
||||
if (ids.includes(id)) {
|
||||
clearTimeout(id)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user