mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
fix(execute-command): address review feedback — double-substitution, timeout:0, route path
- Rewrite resolveTagVariables to use index-based back-to-front splicing instead of global regex replace, preventing double-substitution when resolved values contain tag-like patterns - Fix timeout:0 bypass by using explicit lower-bound check instead of destructuring default - Add shell injection warning to bestPractices documentation - Move API route from api/execute-command/ to api/tools/execute-command/ for consistency
This commit is contained in:
@@ -98,16 +98,16 @@ function resolveTagVariables(
|
||||
blockNameMapping: Record<string, string>,
|
||||
blockOutputSchemas: Record<string, OutputSchema>
|
||||
): string {
|
||||
let resolved = command
|
||||
|
||||
const tagPattern = new RegExp(
|
||||
`${REFERENCE.START}([a-zA-Z_](?:[a-zA-Z0-9_${REFERENCE.PATH_DELIMITER}]*[a-zA-Z0-9_])?)${REFERENCE.END}`,
|
||||
'g'
|
||||
)
|
||||
const tagMatches = resolved.match(tagPattern) || []
|
||||
|
||||
for (const match of tagMatches) {
|
||||
const tagName = match.slice(REFERENCE.START.length, -REFERENCE.END.length).trim()
|
||||
const replacements: Array<{ match: string; index: number; value: string }> = []
|
||||
let match: RegExpExecArray | null
|
||||
|
||||
while ((match = tagPattern.exec(command)) !== null) {
|
||||
const tagName = match[1].trim()
|
||||
const pathParts = tagName.split(REFERENCE.PATH_DELIMITER)
|
||||
const blockName = pathParts[0]
|
||||
const fieldPath = pathParts.slice(1)
|
||||
@@ -131,7 +131,13 @@ function resolveTagVariables(
|
||||
stringValue = String(result.value)
|
||||
}
|
||||
|
||||
resolved = resolved.replace(new RegExp(escapeRegExp(match), 'g'), () => stringValue)
|
||||
replacements.push({ match: match[0], index: match.index, value: stringValue })
|
||||
}
|
||||
|
||||
let resolved = command
|
||||
for (let i = replacements.length - 1; i >= 0; i--) {
|
||||
const { match: matchStr, index, value } = replacements[i]
|
||||
resolved = resolved.slice(0, index) + value + resolved.slice(index + matchStr.length)
|
||||
}
|
||||
|
||||
return resolved
|
||||
@@ -243,7 +249,6 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
const {
|
||||
command,
|
||||
timeout = DEFAULT_EXECUTION_TIMEOUT_MS,
|
||||
workingDirectory,
|
||||
envVars = {},
|
||||
blockData = {},
|
||||
@@ -253,6 +258,9 @@ export async function POST(req: NextRequest) {
|
||||
workflowId,
|
||||
} = body
|
||||
|
||||
const parsedTimeout = Number(body.timeout)
|
||||
const timeout = parsedTimeout > 0 ? parsedTimeout : DEFAULT_EXECUTION_TIMEOUT_MS
|
||||
|
||||
if (!command || typeof command !== 'string') {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Command is required and must be a string' },
|
||||
@@ -17,6 +17,7 @@ export const ExecuteCommandBlock: BlockConfig<ExecuteCommandOutput> = {
|
||||
- Use {{ENV_VAR}} syntax to reference environment variables.
|
||||
- The working directory defaults to the server process directory if not specified.
|
||||
- A non-zero exit code is returned as data (exitCode > 0), not treated as a workflow error. Use a Condition block to branch on exitCode if needed.
|
||||
- Variable values from other blocks are interpolated directly into the command string. Avoid passing untrusted user input as block references to prevent shell injection.
|
||||
`,
|
||||
docsLink: 'https://docs.sim.ai/blocks/execute-command',
|
||||
category: 'blocks',
|
||||
|
||||
@@ -67,7 +67,7 @@ export const executeCommandRunTool: ToolConfig<ExecuteCommandInput, ExecuteComma
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/execute-command/run',
|
||||
url: '/api/tools/execute-command/run',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
Reference in New Issue
Block a user