feat(blocks): add execute command block for self-hosted shell execution#3426
feat(blocks): add execute command block for self-hosted shell execution#3426waleedlatif1 wants to merge 15 commits intostagingfrom
Conversation
PR SummaryHigh Risk Overview Introduces Written by Cursor Bugbot for commit 5782b68. Configure here. |
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
2dd1ff9 to
6802245
Compare
|
@cursor review |
|
@greptile |
Greptile SummaryThis PR adds an Execute Command block that runs arbitrary shell commands on the host machine via Several issues raised in the prior review round appear to have been addressed: the index-based string splicing ( Four key concerns remain that should be addressed:
The feature is self-hosted only and behind a feature flag, which limits blast radius. However, the timeout/MAX_DURATION gap and the envVars dual-use mismatch are real operational concerns that should be addressed before wide adoption. Confidence Score: 3/5
Last reviewed commit: 5782b68 |
…t detection, bestPractices text
|
@greptile |
|
@cursor review |
…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
…ead of throwing When a command times out or exceeds the buffer limit, the route returns partial stdout/stderr. Previously the handler threw an Error, discarding that output. Now returns partial output with an error field so downstream blocks can inspect it. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@greptile |
|
@cursor review |
…n, remove unused userId Fix asymmetric normalization in resolveWorkflowVariables where the stored variable name was normalized but the reference name was only trimmed. This caused <variable.MyVar> to fail matching a variable named "MyVar". Applied the same fix to the function route which had the identical bug. Also removed unused userId field from the execute-command tool config request body — auth identity comes from checkInternalAuth, not the body. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@greptile |
|
@cursor review |
Refactor variable resolution to collect all replacements from all three
patterns (workflow variables, env vars, block references) against the
ORIGINAL command string, then apply them in a single back-to-front pass.
Previously, three sequential passes meant a workflow variable value
containing {{ENV_VAR}} syntax would be further resolved in the env var
pass. Now all patterns are matched before any substitution occurs,
preventing unintended cascading resolution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@greptile |
|
@cursor review |
Return NodeJS.ProcessEnv from getSafeBaseEnv() and include NODE_ENV to satisfy the exec() type signature which requires ProcessEnv. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…verride - Throw a clear error when a block reference cannot be resolved instead of leaving raw <blockName.output> in the command (which the shell interprets as I/O redirection). Skip special prefixes (variable, loop, parallel) which are handled by their own collectors. - Filter user-supplied env vars to prevent overriding safe base env keys (PATH, HOME, SHELL, etc.) and block process-influencing variables (LD_PRELOAD, BASH_ENV, DYLD_INSERT_LIBRARIES, etc.). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@greptile |
|
@cursor review |
apps/sim/executor/handlers/execute-command/execute-command-handler.ts
Outdated
Show resolved
Hide resolved
… regex - Extend BLOCKED_ENV_KEYS with NODE_OPTIONS, LD_AUDIT, DYLD_LIBRARY_PATH, DYLD_FRAMEWORK_PATH, GLIBC_TUNABLES, NODE_PATH, PYTHONPATH, PERL5LIB, RUBYLIB to prevent process-influencing env var injection. - Add error field to block and tool output schemas so downstream blocks can inspect timeout/buffer errors via <executeCommand.error>. Handler returns error: null on success for consistency. - Use permissive tag regex ([^<>]+) matching createReferencePattern() so block names with hyphens (e.g. <my-block.output>) resolve correctly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@greptile |
|
@cursor review |
…list Add two more process-influencing env vars to the blocklist: - JAVA_TOOL_OPTIONS: allows JVM agent injection (similar to NODE_OPTIONS) - PERL5OPT: allows Perl code injection (complements PERL5LIB) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ap 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>
|
@greptile |
|
@cursor review |
| const result = await executeCommand(resolvedCommand, { | ||
| timeout, | ||
| cwd: workingDirectory, | ||
| env: envVars, | ||
| }) |
There was a problem hiding this comment.
envVars used for both template resolution and child process environment — silently filters blocked keys in one path but not the other
The same envVars object serves two purposes: (1) template substitution in resolveCommandVariables (lines 353–360) and (2) as the child process environment via filterUserEnv (line 263). When a workflow env var has a name in BLOCKED_ENV_KEYS (e.g., PATH, HOME), it will:
- Resolve correctly in template patterns like
echo {{PATH}} - Be silently dropped from the child process environment
Concrete example: User sets envVars.PATH = "/custom/bin" and writes command echo {{PATH}} && echo $PATH. The resolved command becomes echo /custom/bin && echo $PATH, but when executed, the process inherits the server's PATH from getSafeBaseEnv(), not the user's custom value. This is a confusing contradiction within a single command.
Consider documenting in bestPractices that workflow env vars matching keys in BLOCKED_ENV_KEYS are available for template substitution but will NOT be injected into the child process environment.
|
|
||
| export const dynamic = 'force-dynamic' | ||
| export const runtime = 'nodejs' | ||
| export const MAX_DURATION = 210 |
There was a problem hiding this comment.
User-configured timeout can exceed MAX_DURATION, orphaning the child process
MAX_DURATION = 210 seconds is the Next.js maximum route duration. However, a user can configure any positive timeout value via the block UI (line 68 in execute-command.ts). When user-configured timeout exceeds MAX_DURATION * 1000:
execstarts the child process with the user's timeout (e.g., 300,000 ms).- At 210 seconds, the Next.js/hosting platform kills the route handler.
- The child process continues running on the host as an orphan process.
- The client receives a 504 / connection-closed error instead of the clean
"Command timed out"response.
This is a real operational problem for self-hosted deployments with long-running build scripts or migrations. Consider enforcing an upper bound on timeout less than MAX_DURATION:
const MAX_ALLOWED_TIMEOUT_MS = (MAX_DURATION - 10) * 1000 // 200 s, leaving buffer
const timeout = Math.min(
parsedTimeout > 0 ? parsedTimeout : DEFAULT_EXECUTION_TIMEOUT_MS,
MAX_ALLOWED_TIMEOUT_MS
)| id: 'workingDirectory', | ||
| title: 'Working Directory', | ||
| type: 'short-input', | ||
| required: false, | ||
| placeholder: '/path/to/directory', | ||
| }, |
There was a problem hiding this comment.
workingDirectory silently ignores variable-reference syntax
Unlike the command field, workingDirectory is passed directly to exec as cwd (route.ts line 364) without going through resolveCommandVariables. This means <blockName.output>, {{ENV_VAR}}, and <variable.name> syntax in the working directory field are treated as literal path strings rather than being substituted.
This is asymmetric and confusing because:
- The
commandfield prominently documents both syntaxes (lines 17–18) - The wand prompt instructs the LLM to use these syntaxes (lines 40–41)
- Users or the LLM are likely to write
workingDirectory: <filesBlock.outputPath>and be surprised when it doesn't resolve
Consider either:
- Passing
workingDirectorythroughresolveCommandVariablesbefore using it ascwd, or - Adding a note to
bestPracticesexplicitly stating that variable-reference syntax is not supported in the Working Directory field.
| if (!result.success) { | ||
| if (result.output) { | ||
| return { | ||
| ...result.output, | ||
| error: result.error || 'Command execution failed', | ||
| } | ||
| } | ||
| throw new Error(result.error || 'Command execution failed') |
There was a problem hiding this comment.
Unreachable throw branch — handler already returns data on failure
The route handler always returns an output object regardless of the success value (timeout case: route.ts lines 376–386, maxBuffer case: lines 388–398, error case: lines 408–418). This means result.output is never falsy when !result.success, making the throw new Error(...) branch at line 53 unreachable in practice.
The dead branch could mislead future readers into thinking there is a failure mode where no output is returned. More importantly, it diverges from the stated n8n-style philosophy that "non-zero exits are data, not errors" — failures should be returned as structured data, not thrown.
Consider removing the unreachable branch and always returning the output for consistency:
if (!result.success) {
return {
...(result.output ?? { stdout: '', stderr: '', exitCode: 1 }),
error: result.error || 'Command execution failed',
}
}
return { ...result.output, error: null }There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
…dler, document workingDirectory - Enforce upper bound on timeout (MAX_DURATION - 10s) to prevent orphan processes - Remove unreachable throw branch in handler — always return structured data - Document that workingDirectory does not support variable references Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
child_process.execEXECUTE_COMMAND_ENABLEDfeature flag, self-hosted only (blocked on hosted)<block.output>,{{ENV_VAR}},<variable.name>)Type of Change
Testing
Tested manually
Checklist