Skip to content

Commit 281d896

Browse files
DanielVenableaduh95
authored andcommitted
child_process: deprecate passing args to spawn and execFile
Accepting `args` gives the false impression that the args are escaped while really they are just concatenated. This makes it easy to introduce bugs and security vulnerabilities.
1 parent 109c817 commit 281d896

6 files changed

+31
-6
lines changed

doc/api/child_process.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ exec('my.bat', (err, stdout, stderr) => {
158158
});
159159

160160
// Script with spaces in the filename:
161-
const bat = spawn('"my script.cmd"', ['a', 'b'], { shell: true });
161+
const bat = spawn('"my script.cmd" a b', { shell: true });
162162
// or:
163163
exec('"my script.cmd" a b', (err, stdout, stderr) => {
164164
// ...

doc/api/deprecations.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3859,13 +3859,16 @@ deprecated, as their values are guaranteed to be identical to that of `process.f
38593859

38603860
<!-- YAML
38613861
changes:
3862+
- version: REPLACEME
3863+
pr-url: https://github.com/nodejs/node/pull/57199
3864+
description: Runtime deprecation.
38623865
- version:
38633866
- REPLACEME
38643867
pr-url: https://github.com/nodejs/node/pull/57389
38653868
description: Documentation-only deprecation.
38663869
-->
38673870

3868-
Type: Documentation-only
3871+
Type: Runtime
38693872

38703873
When an `args` array is passed to [`child_process.execFile`][] or [`child_process.spawn`][] with the option
38713874
`{ shell: true }`, the values are not escaped, only space-separated, which can lead to shell injection.

lib/child_process.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,13 @@ function normalizeSpawnArguments(file, args, options) {
611611

612612
if (options.shell) {
613613
validateArgumentNullCheck(options.shell, 'options.shell');
614+
if (args.length > 0) {
615+
process.emitWarning(
616+
'Passing args to a child process with shell option true can lead to security ' +
617+
'vulnerabilities, as the arguments are not escaped, only concatenated.',
618+
'DeprecationWarning',
619+
'DEP0190');
620+
}
614621
const command = ArrayPrototypeJoin([file, ...args], ' ');
615622
// Set the shell, switches, and commands.
616623
if (process.platform === 'win32') {

test/parallel/test-child-process-execfile.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@ const execOpts = { encoding: 'utf8', shell: true, env: { ...process.env, NODE: p
4747
{
4848
// Verify the shell option works properly
4949
execFile(
50-
`"${common.isWindows ? execOpts.env.NODE : '$NODE'}"`,
51-
[`"${common.isWindows ? execOpts.env.FIXTURE : '$FIXTURE'}"`, 0],
50+
common.isWindows ? `"${execOpts.env.NODE}" "${execOpts.env.FIXTURE} 0` : `"$NODE" "$FIXTURE" 0`,
5251
execOpts,
5352
common.mustSucceed(),
5453
);
@@ -117,10 +116,14 @@ const execOpts = { encoding: 'utf8', shell: true, env: { ...process.env, NODE: p
117116
...(common.isWindows ? [] : [{ encoding: 'utf8' }]),
118117
{ shell: true, encoding: 'utf8' },
119118
].forEach((options) => {
120-
const execFileSyncStdout = execFileSync(file, args, options);
119+
const command = options.shell ?
120+
[[file, ...args].join(' ')] :
121+
[file, args];
122+
123+
const execFileSyncStdout = execFileSync(...command, options);
121124
assert.strictEqual(execFileSyncStdout, `foo bar${os.EOL}`);
122125

123-
execFile(file, args, options, common.mustCall((_, stdout) => {
126+
execFile(...command, options, common.mustCall((_, stdout) => {
124127
assert.strictEqual(stdout, execFileSyncStdout);
125128
}));
126129
});

test/parallel/test-child-process-spawn-shell.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ doesNotExist.on('exit', common.mustCall((code, signal) => {
1818
}));
1919

2020
// Verify that passing arguments works
21+
common.expectWarning(
22+
'DeprecationWarning',
23+
'Passing args to a child process with shell option true can lead to security ' +
24+
'vulnerabilities, as the arguments are not escaped, only concatenated.',
25+
'DEP0190');
26+
2127
const echo = cp.spawn('echo', ['foo'], {
2228
encoding: 'utf8',
2329
shell: true

test/parallel/test-child-process-spawnsync-shell.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ else
1919
assert.strictEqual(doesNotExist.status, 127); // Exit code of /bin/sh
2020

2121
// Verify that passing arguments works
22+
common.expectWarning(
23+
'DeprecationWarning',
24+
'Passing args to a child process with shell option true can lead to security ' +
25+
'vulnerabilities, as the arguments are not escaped, only concatenated.',
26+
'DEP0190');
27+
2228
internalCp.spawnSync = common.mustCall(function(opts) {
2329
assert.strictEqual(opts.args[opts.args.length - 1].replace(/"/g, ''),
2430
'echo foo');

0 commit comments

Comments
 (0)