🔒 Use vm instead of Function in Node.js (#1509)
This commit is contained in:
@ -17,7 +17,6 @@ import { getChatCompletionStream } from '@typebot.io/bot-engine/blocks/integrati
|
|||||||
import { ChatCompletionOpenAIOptions } from '@typebot.io/schemas/features/blocks/integrations/openai/schema'
|
import { ChatCompletionOpenAIOptions } from '@typebot.io/schemas/features/blocks/integrations/openai/schema'
|
||||||
import { isForgedBlockType } from '@typebot.io/schemas/features/blocks/forged/helpers'
|
import { isForgedBlockType } from '@typebot.io/schemas/features/blocks/forged/helpers'
|
||||||
|
|
||||||
export const runtime = 'edge'
|
|
||||||
export const preferredRegion = 'lhr1'
|
export const preferredRegion = 'lhr1'
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import { SessionState } from '@typebot.io/schemas/features/chat/sessionState'
|
|||||||
import { ExecuteIntegrationResponse } from '../../../types'
|
import { ExecuteIntegrationResponse } from '../../../types'
|
||||||
import { parseVariables } from '@typebot.io/variables/parseVariables'
|
import { parseVariables } from '@typebot.io/variables/parseVariables'
|
||||||
import { updateVariablesInSession } from '@typebot.io/variables/updateVariablesInSession'
|
import { updateVariablesInSession } from '@typebot.io/variables/updateVariablesInSession'
|
||||||
|
import vm from 'vm'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
state: SessionState
|
state: SessionState
|
||||||
@ -55,12 +56,14 @@ export const resumeWebhookExecution = ({
|
|||||||
if (!varMapping?.bodyPath || !varMapping.variableId) return newVariables
|
if (!varMapping?.bodyPath || !varMapping.variableId) return newVariables
|
||||||
const existingVariable = typebot.variables.find(byId(varMapping.variableId))
|
const existingVariable = typebot.variables.find(byId(varMapping.variableId))
|
||||||
if (!existingVariable) return newVariables
|
if (!existingVariable) return newVariables
|
||||||
const func = Function(
|
const sandbox = vm.createContext({
|
||||||
'data',
|
data: response.data,
|
||||||
`return data.${parseVariables(typebot.variables)(varMapping?.bodyPath)}`
|
})
|
||||||
)
|
|
||||||
try {
|
try {
|
||||||
const value: unknown = func(response)
|
const value: unknown = vm.runInContext(
|
||||||
|
parseVariables(typebot.variables)(varMapping?.bodyPath),
|
||||||
|
sandbox
|
||||||
|
)
|
||||||
return [...newVariables, { ...existingVariable, value }]
|
return [...newVariables, { ...existingVariable, value }]
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return newVariables
|
return newVariables
|
||||||
|
@ -7,6 +7,7 @@ import { parseVariables } from '@typebot.io/variables/parseVariables'
|
|||||||
import { updateVariablesInSession } from '@typebot.io/variables/updateVariablesInSession'
|
import { updateVariablesInSession } from '@typebot.io/variables/updateVariablesInSession'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { utcToZonedTime, format as tzFormat } from 'date-fns-tz'
|
import { utcToZonedTime, format as tzFormat } from 'date-fns-tz'
|
||||||
|
import vm from 'vm'
|
||||||
|
|
||||||
export const executeSetVariable = (
|
export const executeSetVariable = (
|
||||||
state: SessionState,
|
state: SessionState,
|
||||||
@ -67,11 +68,16 @@ const evaluateSetVariableExpression =
|
|||||||
// To avoid octal number evaluation
|
// To avoid octal number evaluation
|
||||||
if (!isNaN(str as unknown as number) && /0[^.].+/.test(str)) return str
|
if (!isNaN(str as unknown as number) && /0[^.].+/.test(str)) return str
|
||||||
const evaluating = parseVariables(variables, { fieldToParse: 'id' })(
|
const evaluating = parseVariables(variables, { fieldToParse: 'id' })(
|
||||||
str.includes('return ') ? str : `return ${str}`
|
`(function() {${str.includes('return ') ? str : 'return ' + str}})()`
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
const func = Function(...variables.map((v) => v.id), evaluating)
|
const sandbox = vm.createContext({
|
||||||
return func(...variables.map((v) => parseGuessedValueType(v.value)))
|
...Object.fromEntries(
|
||||||
|
variables.map((v) => [v.id, parseGuessedValueType(v.value)])
|
||||||
|
),
|
||||||
|
fetch,
|
||||||
|
})
|
||||||
|
return vm.runInContext(evaluating, sandbox)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return parseVariables(variables)(str)
|
return parseVariables(variables)(str)
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ export const runChatCompletionStream = async ({
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const { output } = await executeFunction({
|
const { output, newVariables } = await executeFunction({
|
||||||
variables: variables.list(),
|
variables: variables.list(),
|
||||||
args:
|
args:
|
||||||
typeof toolCall.func.arguments === 'string'
|
typeof toolCall.func.arguments === 'string'
|
||||||
@ -84,8 +84,7 @@ export const runChatCompletionStream = async ({
|
|||||||
body: toolDefinition.code,
|
body: toolDefinition.code,
|
||||||
})
|
})
|
||||||
|
|
||||||
// TO-DO: enable once we're out of edge runtime.
|
newVariables?.forEach((v) => variables.set(v.id, v.value))
|
||||||
// newVariables?.forEach((v) => variables.set(v.id, v.value))
|
|
||||||
|
|
||||||
const newMessages = appendToolCallMessage({
|
const newMessages = appendToolCallMessage({
|
||||||
tool_call_id: toolCall.id,
|
tool_call_id: toolCall.id,
|
||||||
|
@ -4,6 +4,7 @@ import { parseGuessedValueType } from './parseGuessedValueType'
|
|||||||
import { isDefined } from '@typebot.io/lib'
|
import { isDefined } from '@typebot.io/lib'
|
||||||
import { safeStringify } from '@typebot.io/lib/safeStringify'
|
import { safeStringify } from '@typebot.io/lib/safeStringify'
|
||||||
import { Variable } from './types'
|
import { Variable } from './types'
|
||||||
|
import vm from 'vm'
|
||||||
|
|
||||||
const defaultTimeout = 10
|
const defaultTimeout = 10
|
||||||
|
|
||||||
@ -18,7 +19,9 @@ export const executeFunction = async ({
|
|||||||
body,
|
body,
|
||||||
args: initialArgs,
|
args: initialArgs,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const parsedBody = parseVariables(variables, { fieldToParse: 'id' })(body)
|
const parsedBody = `(async function() {${parseVariables(variables, {
|
||||||
|
fieldToParse: 'id',
|
||||||
|
})(body)}})()`
|
||||||
|
|
||||||
const args = (
|
const args = (
|
||||||
extractVariablesFromText(variables)(body).map((variable) => ({
|
extractVariablesFromText(variables)(body).map((variable) => ({
|
||||||
@ -30,22 +33,25 @@ export const executeFunction = async ({
|
|||||||
? Object.entries(initialArgs).map(([id, value]) => ({ id, value }))
|
? Object.entries(initialArgs).map(([id, value]) => ({ id, value }))
|
||||||
: []
|
: []
|
||||||
)
|
)
|
||||||
const func = AsyncFunction(
|
|
||||||
...args.map(({ id }) => id),
|
|
||||||
'setVariable',
|
|
||||||
parsedBody
|
|
||||||
)
|
|
||||||
|
|
||||||
let updatedVariables: Record<string, any> = {}
|
let updatedVariables: Record<string, any> = {}
|
||||||
|
|
||||||
const setVariable = (key: string, value: any) => {
|
const setVariable = (key: string, value: any) => {
|
||||||
updatedVariables[key] = value
|
updatedVariables[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const context = vm.createContext({
|
||||||
|
...Object.fromEntries(args.map(({ id, value }) => [id, value])),
|
||||||
|
setVariable,
|
||||||
|
fetch,
|
||||||
|
setTimeout,
|
||||||
|
})
|
||||||
|
|
||||||
const timeout = new Timeout()
|
const timeout = new Timeout()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const output: unknown = await timeout.wrap(
|
const output: unknown = await timeout.wrap(
|
||||||
func(...args.map(({ value }) => value), setVariable),
|
await vm.runInContext(parsedBody, context),
|
||||||
defaultTimeout * 1000
|
defaultTimeout * 1000
|
||||||
)
|
)
|
||||||
timeout.clear()
|
timeout.clear()
|
||||||
@ -81,8 +87,6 @@ export const executeFunction = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
|
|
||||||
|
|
||||||
class Timeout {
|
class Timeout {
|
||||||
private ids: NodeJS.Timeout[]
|
private ids: NodeJS.Timeout[]
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import { safeStringify } from '@typebot.io/lib/safeStringify'
|
|||||||
import { isDefined, isNotDefined } from '@typebot.io/lib/utils'
|
import { isDefined, isNotDefined } from '@typebot.io/lib/utils'
|
||||||
import { parseGuessedValueType } from './parseGuessedValueType'
|
import { parseGuessedValueType } from './parseGuessedValueType'
|
||||||
import { Variable, VariableWithValue } from './types'
|
import { Variable, VariableWithValue } from './types'
|
||||||
|
import vm from 'vm'
|
||||||
|
|
||||||
export type ParseVariablesOptions = {
|
export type ParseVariablesOptions = {
|
||||||
fieldToParse?: 'value' | 'id'
|
fieldToParse?: 'value' | 'id'
|
||||||
@ -73,11 +74,17 @@ const evaluateInlineCode = (
|
|||||||
{ variables }: { variables: Variable[] }
|
{ variables }: { variables: Variable[] }
|
||||||
) => {
|
) => {
|
||||||
const evaluating = parseVariables(variables, { fieldToParse: 'id' })(
|
const evaluating = parseVariables(variables, { fieldToParse: 'id' })(
|
||||||
code.includes('return ') ? code : `return ${code}`
|
`(async function() {${
|
||||||
|
code.includes('return ') ? code : 'return ' + code
|
||||||
|
}})()`
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
const func = Function(...variables.map((v) => v.id), evaluating)
|
const sandbox = vm.createContext({
|
||||||
return func(...variables.map((v) => parseGuessedValueType(v.value)))
|
...Object.fromEntries(
|
||||||
|
variables.map((v) => [v.id, parseGuessedValueType(v.value)])
|
||||||
|
),
|
||||||
|
})
|
||||||
|
return vm.runInContext(evaluating, sandbox)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return parseVariables(variables)(code)
|
return parseVariables(variables)(code)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user