fix(rce): add 'isolate' to list of trusted deps, fixed custom tools environment resolution (#2387)

* fix(rce): add isolate to list of trusted deps

* updated error enchancer in RCE

* fixed

* fix build

* fix failing test

* fix build

* fix build

* remove extraneous comment
This commit is contained in:
Waleed
2025-12-15 15:24:11 -08:00
committed by GitHub
parent 3a5363ac54
commit a45bb1bf3b
14 changed files with 547 additions and 324 deletions

View File

@@ -5,6 +5,10 @@ import { executeInE2B } from '@/lib/execution/e2b'
import { executeInIsolatedVM } from '@/lib/execution/isolated-vm'
import { CodeLanguage, DEFAULT_CODE_LANGUAGE, isValidCodeLanguage } from '@/lib/execution/languages'
import { createLogger } from '@/lib/logs/console/logger'
import {
createEnvVarPattern,
createWorkflowVariablePattern,
} from '@/executor/utils/reference-validation'
export const dynamic = 'force-dynamic'
export const runtime = 'nodejs'
@@ -321,9 +325,9 @@ function createUserFriendlyErrorMessage(
): string {
let errorMessage = enhanced.message
// Add line and column information if available
// Add line information if available
if (enhanced.line !== undefined) {
let lineInfo = `Line ${enhanced.line}${enhanced.column !== undefined ? `:${enhanced.column}` : ''}`
let lineInfo = `Line ${enhanced.line}`
// Add the actual line content if available
if (enhanced.lineContent) {
@@ -337,8 +341,7 @@ function createUserFriendlyErrorMessage(
const stackMatch = enhanced.stack.match(/user-function\.js:(\d+)(?::(\d+))?/)
if (stackMatch) {
const line = Number.parseInt(stackMatch[1], 10)
const column = stackMatch[2] ? Number.parseInt(stackMatch[2], 10) : undefined
let lineInfo = `Line ${line}${column ? `:${column}` : ''}`
let lineInfo = `Line ${line}`
// Try to get line content if we have userCode
if (userCode) {
@@ -375,27 +378,6 @@ function createUserFriendlyErrorMessage(
}
}
// For syntax errors, provide additional context
if (enhanced.name === 'SyntaxError') {
if (errorMessage.includes('Invalid or unexpected token')) {
errorMessage += ' (Check for missing quotes, brackets, or semicolons)'
} else if (errorMessage.includes('Unexpected end of input')) {
errorMessage += ' (Check for missing closing brackets or braces)'
} else if (errorMessage.includes('Unexpected token')) {
// Check if this might be due to incomplete code
if (
enhanced.lineContent &&
((enhanced.lineContent.includes('(') && !enhanced.lineContent.includes(')')) ||
(enhanced.lineContent.includes('[') && !enhanced.lineContent.includes(']')) ||
(enhanced.lineContent.includes('{') && !enhanced.lineContent.includes('}')))
) {
errorMessage += ' (Check for missing closing parentheses, brackets, or braces)'
} else {
errorMessage += ' (Check your syntax)'
}
}
}
return errorMessage
}
@@ -409,19 +391,27 @@ function resolveWorkflowVariables(
): string {
let resolvedCode = code
const variableMatches = resolvedCode.match(/<variable\.([^>]+)>/g) || []
for (const match of variableMatches) {
const variableName = match.slice('<variable.'.length, -1).trim()
const regex = createWorkflowVariablePattern()
let match: RegExpExecArray | null
const replacements: Array<{
match: string
index: number
variableName: string
variableValue: unknown
}> = []
while ((match = regex.exec(code)) !== null) {
const variableName = match[1].trim()
// Find the variable by name (workflowVariables is indexed by ID, values are variable objects)
const foundVariable = Object.entries(workflowVariables).find(
([_, variable]) => (variable.name || '').replace(/\s+/g, '') === variableName
)
let variableValue: unknown = ''
if (foundVariable) {
const variable = foundVariable[1]
// Get the typed value - handle different variable types
let variableValue = variable.value
variableValue = variable.value
if (variable.value !== undefined && variable.value !== null) {
try {
@@ -443,22 +433,30 @@ function resolveWorkflowVariables(
// Keep original value if JSON parsing fails
}
}
} catch (error) {
} catch {
// Fallback to original value on error
variableValue = variable.value
}
}
// Create a safe variable reference
const safeVarName = `__variable_${variableName.replace(/[^a-zA-Z0-9_]/g, '_')}`
contextVariables[safeVarName] = variableValue
// Replace the variable reference with the safe variable name
resolvedCode = resolvedCode.replace(new RegExp(escapeRegExp(match), 'g'), safeVarName)
} else {
// Variable not found - replace with empty string to avoid syntax errors
resolvedCode = resolvedCode.replace(new RegExp(escapeRegExp(match), 'g'), '')
}
replacements.push({
match: match[0],
index: match.index,
variableName,
variableValue,
})
}
// Process replacements in reverse order to maintain correct indices
for (let i = replacements.length - 1; i >= 0; i--) {
const { match: matchStr, index, variableName, variableValue } = replacements[i]
// Use variable reference approach
const safeVarName = `__variable_${variableName.replace(/[^a-zA-Z0-9_]/g, '_')}`
contextVariables[safeVarName] = variableValue
resolvedCode =
resolvedCode.slice(0, index) + safeVarName + resolvedCode.slice(index + matchStr.length)
}
return resolvedCode
@@ -475,18 +473,29 @@ function resolveEnvironmentVariables(
): string {
let resolvedCode = code
const envVarMatches = resolvedCode.match(/\{\{([^}]+)\}\}/g) || []
for (const match of envVarMatches) {
const varName = match.slice(2, -2).trim()
// Priority: 1. Environment variables from workflow, 2. Params
const varValue = envVars[varName] || params[varName] || ''
const regex = createEnvVarPattern()
let match: RegExpExecArray | null
const replacements: Array<{ match: string; index: number; varName: string; varValue: string }> =
[]
while ((match = regex.exec(code)) !== null) {
const varName = match[1].trim()
const varValue = envVars[varName] || params[varName] || ''
replacements.push({
match: match[0],
index: match.index,
varName,
varValue: String(varValue),
})
}
for (let i = replacements.length - 1; i >= 0; i--) {
const { match: matchStr, index, varName, varValue } = replacements[i]
// Instead of injecting large JSON directly, create a variable reference
const safeVarName = `__var_${varName.replace(/[^a-zA-Z0-9_]/g, '_')}`
contextVariables[safeVarName] = varValue
// Replace the template with a variable reference
resolvedCode = resolvedCode.replace(new RegExp(escapeRegExp(match), 'g'), safeVarName)
resolvedCode =
resolvedCode.slice(0, index) + safeVarName + resolvedCode.slice(index + matchStr.length)
}
return resolvedCode
@@ -905,18 +914,22 @@ export async function POST(req: NextRequest) {
const executionTime = Date.now() - startTime
if (isolatedResult.error) {
const errorObj = {
message: isolatedResult.error.message,
name: isolatedResult.error.name,
stack: isolatedResult.error.stack,
}
logger.error(`[${requestId}] Function execution failed in isolated-vm`, {
error: isolatedResult.error,
executionTime,
})
const enhancedError = extractEnhancedError(errorObj, userCodeStartLine, resolvedCode)
const ivmError = isolatedResult.error
const enhancedError: EnhancedError = {
message: ivmError.message,
name: ivmError.name,
stack: ivmError.stack,
originalError: ivmError,
line: ivmError.line,
column: ivmError.column,
lineContent: ivmError.lineContent,
}
const userFriendlyErrorMessage = createUserFriendlyErrorMessage(
enhancedError,
requestId,
@@ -924,13 +937,12 @@ export async function POST(req: NextRequest) {
)
logger.error(`[${requestId}] Enhanced error details`, {
originalMessage: errorObj.message,
originalMessage: ivmError.message,
enhancedMessage: userFriendlyErrorMessage,
line: enhancedError.line,
column: enhancedError.column,
lineContent: enhancedError.lineContent,
errorType: enhancedError.name,
userCodeStartLine,
})
return NextResponse.json(
@@ -978,7 +990,6 @@ export async function POST(req: NextRequest) {
resolvedCode
)
// Log enhanced error details for debugging
logger.error(`[${requestId}] Enhanced error details`, {
originalMessage: error.message,
enhancedMessage: userFriendlyErrorMessage,
@@ -997,7 +1008,6 @@ export async function POST(req: NextRequest) {
stdout: cleanStdout(stdout),
executionTime,
},
// Include debug information in development or for debugging
debug: {
line: enhancedError.line,
column: enhancedError.column,