Summary
eslint-factory/src/rules/no-child-process-interpolated-command.ts detects interpolated/dynamic command strings passed to child_process exec-family methods (exec, execSync, spawn, spawnSync, execFile, execFileSync), but its method-resolution logic only recognizes the callee object when it is a plain Identifier bound to a require'd/imported child_process module (see isChildProcessObjectBinding in eslint-factory/src/rules/try-catch-rule-utils.ts, and the callee.object.type === AST_NODE_TYPES.Identifier check in no-child-process-interpolated-command.ts around line 135-140).
An inline call of the form:
require('child_process').execSync(`rm -rf ${userInput}`);
never binds the child_process module to an identifier - the callee object is itself a CallExpression (the require(...) call), not an Identifier - so isChildProcessObjectBinding/resolveChildProcessMethod never matches, and the rule silently does not fire, even though this is a textbook command-injection sink identical to the const cp = require('child_process'); cp.execSync(...) pattern the rule already correctly flags.
Grounding
No live occurrence of this exact inline-require call shape was found in the current actions/setup/js/**/*.cjs corpus (searched via a pattern matching require('child_process'). or require('node:child_process'). followed directly by exec/execSync/spawn/spawnSync/execFile/execFileSync), so this is a soundness gap rather than an active false negative today. It's filed because it's cheap to close and consistent with the rule's own stated intent (catching command-injection sinks reached through the child_process module regardless of how it's referenced), and because the corpus is disciplined today but a future contributor could easily write the inline form without the rule catching it.
Acceptance Criteria
- Rule recognizes
require('child_process').<method>(...) / require('node:child_process').<method>(...) as a valid child_process method call, in addition to the existing identifier-binding-based resolution.
- A new invalid test case is added to
no-child-process-interpolated-command.test.ts covering require('child_process').execSync(...) (and ideally spawn) with an interpolated/dynamic command argument, asserting the rule reports it.
- A valid test case confirms non-interpolated/static commands via the same inline-require form are still accepted (no false positive introduced).
- Existing tests continue to pass; no changes to the rule's shell-true/argv-based sub-checks for
spawn/spawnSync/execFile/execFileSync are required beyond the callee-resolution fix.
Scope note
This mirrors a similar shape of gap previously fixed for other rules in this family (destructured/aliased fs bindings in require-fs-sync-try-catch) - a single missing identifier-resolution path for an otherwise well-covered rule.
Generated by 🤖 ESLint Refiner · agent · 236.9 AIC · ⌖ 5.27 AIC · ⊞ 4.9K · ◷
Summary
eslint-factory/src/rules/no-child-process-interpolated-command.tsdetects interpolated/dynamic command strings passed tochild_processexec-family methods (exec,execSync,spawn,spawnSync,execFile,execFileSync), but its method-resolution logic only recognizes the callee object when it is a plainIdentifierbound to a require'd/importedchild_processmodule (seeisChildProcessObjectBindingineslint-factory/src/rules/try-catch-rule-utils.ts, and thecallee.object.type === AST_NODE_TYPES.Identifiercheck inno-child-process-interpolated-command.tsaround line 135-140).An inline call of the form:
never binds the
child_processmodule to an identifier - the callee object is itself aCallExpression(therequire(...)call), not anIdentifier- soisChildProcessObjectBinding/resolveChildProcessMethodnever matches, and the rule silently does not fire, even though this is a textbook command-injection sink identical to theconst cp = require('child_process'); cp.execSync(...)pattern the rule already correctly flags.Grounding
No live occurrence of this exact inline-require call shape was found in the current
actions/setup/js/**/*.cjscorpus (searched via a pattern matchingrequire('child_process').orrequire('node:child_process').followed directly by exec/execSync/spawn/spawnSync/execFile/execFileSync), so this is a soundness gap rather than an active false negative today. It's filed because it's cheap to close and consistent with the rule's own stated intent (catching command-injection sinks reached through thechild_processmodule regardless of how it's referenced), and because the corpus is disciplined today but a future contributor could easily write the inline form without the rule catching it.Acceptance Criteria
require('child_process').<method>(...)/require('node:child_process').<method>(...)as a valid child_process method call, in addition to the existing identifier-binding-based resolution.no-child-process-interpolated-command.test.tscoveringrequire('child_process').execSync(...)(and ideallyspawn) with an interpolated/dynamic command argument, asserting the rule reports it.spawn/spawnSync/execFile/execFileSyncare required beyond the callee-resolution fix.Scope note
This mirrors a similar shape of gap previously fixed for other rules in this family (destructured/aliased
fsbindings inrequire-fs-sync-try-catch) - a single missing identifier-resolution path for an otherwise well-covered rule.