fix(execute-command): static import, user-configurable timeout, overlap guard, shell-safe regex

- Promote DEFAULT_EXECUTION_TIMEOUT_MS to static top-level import
- Add timeout subBlock so users can configure command timeout
- Add overlapping replacement assertion to prevent corruption
- Tighten tag regex to require non-whitespace start, avoiding shell redirection false matches

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Waleed Latif
2026-03-05 14:48:50 -08:00
parent c4cb2aaf06
commit 5782b6830e
3 changed files with 22 additions and 3 deletions

View File

@@ -4,6 +4,7 @@ import { type NextRequest, NextResponse } from 'next/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { isExecuteCommandEnabled } from '@/lib/core/config/feature-flags'
import { generateRequestId } from '@/lib/core/utils/request'
import { DEFAULT_EXECUTION_TIMEOUT_MS } from '@/lib/execution/constants'
import { normalizeName, REFERENCE, SPECIAL_REFERENCE_PREFIXES } from '@/executor/constants'
import { type OutputSchema, resolveBlockReference } from '@/executor/utils/block-reference'
import {
@@ -151,7 +152,7 @@ function collectTagReplacements(
blockOutputSchemas: Record<string, OutputSchema>
): Replacement[] {
const tagPattern = new RegExp(
`${REFERENCE.START}([^${REFERENCE.START}${REFERENCE.END}]+)${REFERENCE.END}`,
`${REFERENCE.START}(\\S[^${REFERENCE.START}${REFERENCE.END}]*)${REFERENCE.END}`,
'g'
)
@@ -217,6 +218,16 @@ function resolveCommandVariables(
allReplacements.sort((a, b) => a.index - b.index)
for (let i = 1; i < allReplacements.length; i++) {
const prev = allReplacements[i - 1]
const curr = allReplacements[i]
if (curr.index < prev.index + prev.length) {
throw new Error(
`Overlapping variable references detected at positions ${prev.index} and ${curr.index}`
)
}
}
let resolved = command
for (let i = allReplacements.length - 1; i >= 0; i--) {
const { index, length, value } = allReplacements[i]
@@ -310,7 +321,6 @@ export async function POST(req: NextRequest) {
}
const body = await req.json()
const { DEFAULT_EXECUTION_TIMEOUT_MS } = await import('@/lib/execution/constants')
const {
command,

View File

@@ -1,5 +1,6 @@
import { TerminalIcon } from '@/components/icons'
import { isTruthy } from '@/lib/core/config/env'
import { DEFAULT_EXECUTION_TIMEOUT_MS } from '@/lib/execution/constants'
import type { BlockConfig } from '@/blocks/types'
import type { ExecuteCommandOutput } from '@/tools/execute-command/types'
@@ -59,6 +60,13 @@ IMPORTANT FORMATTING RULES:
required: false,
placeholder: '/path/to/directory',
},
{
id: 'timeout',
title: 'Timeout (ms)',
type: 'short-input',
required: false,
placeholder: String(DEFAULT_EXECUTION_TIMEOUT_MS),
},
],
tools: {
access: ['execute_command_run'],
@@ -66,6 +74,7 @@ IMPORTANT FORMATTING RULES:
inputs: {
command: { type: 'string', description: 'Shell command to execute' },
workingDirectory: { type: 'string', description: 'Working directory for the command' },
timeout: { type: 'number', description: 'Execution timeout in milliseconds' },
},
outputs: {
stdout: { type: 'string', description: 'Standard output from the command' },

View File

@@ -19,7 +19,7 @@ export const executeCommandRunTool: ToolConfig<ExecuteCommandInput, ExecuteComma
timeout: {
type: 'number',
required: false,
visibility: 'hidden',
visibility: 'user-or-llm',
description: 'Execution timeout in milliseconds',
default: DEFAULT_EXECUTION_TIMEOUT_MS,
},