Skip to content

Commit b3322f3

Browse files
committed
child_process: deprecate passing options.shell as empty string
1 parent 7618c6d commit b3322f3

File tree

3 files changed

+48
-6
lines changed

3 files changed

+48
-6
lines changed

doc/api/deprecations.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3952,6 +3952,30 @@ Type: Documentation-only
39523952
The support for priority signaling has been deprecated in the [RFC 9113][], and
39533953
will be removed in future versions of Node.js.
39543954

3955+
### DEP0195: Calling `node:child_process` functions with `options.shell` as an empty string
3956+
3957+
<!-- YAML
3958+
changes:
3959+
- version: REPLACEME
3960+
pr-url: https://github.com/nodejs/node/pull/58525
3961+
description: Documentation-only deprecation
3962+
with `--pending-deprecation` support.
3963+
-->
3964+
3965+
Type: Documentation-only (supports [`--pending-deprecation`][])
3966+
3967+
Calling the process-spawning functions with `{ shell: '' }` is almost certainly
3968+
unintentional, and can cause aberrant behavior.
3969+
3970+
To make [`child_process.execFile`][] or [`child_process.spawn`][] invoke the
3971+
default shell, use `{ shell: true }`. If the intention is not to invoke a shell
3972+
(default behavior), either omit the `shell` option, or set it to `false` or a
3973+
nullish value.
3974+
3975+
To make [`child_process.exec`][] invoke the default shell, either omit the
3976+
`shell` option, or set it to a nullish value. If the intention is not to invoke
3977+
a shell, use [`child_process.execFile`][] instead.
3978+
39553979
[DEP0142]: #dep0142-repl_builtinlibs
39563980
[NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
39573981
[RFC 6066]: https://tools.ietf.org/html/rfc6066#section-3
@@ -3980,6 +4004,7 @@ will be removed in future versions of Node.js.
39804004
[`asyncResource.runInAsyncScope()`]: async_context.md#asyncresourceruninasyncscopefn-thisarg-args
39814005
[`buffer.subarray`]: buffer.md#bufsubarraystart-end
39824006
[`child_process.execFile`]: child_process.md#child_processexecfilefile-args-options-callback
4007+
[`child_process.exec`]: child_process.md#child_processexeccommand-options-callback
39834008
[`child_process.spawn`]: child_process.md#child_processspawncommand-args-options
39844009
[`child_process`]: child_process.md
39854010
[`clearInterval()`]: timers.md#clearintervaltimeout

lib/child_process.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const {
4646
SymbolDispose,
4747
} = primordials;
4848

49+
const { getOptionValue } = require('internal/options');
4950
const {
5051
assignFunctionName,
5152
convertToValidSignal,
@@ -543,6 +544,8 @@ function copyProcessEnvToEnv(env, name, optionEnv) {
543544
}
544545

545546
let emittedDEP0190Already = false;
547+
let emittedDEP0195Already = false;
548+
const pendingDeprecation = getOptionValue('--pending-deprecation');
546549
function normalizeSpawnArguments(file, args, options) {
547550
validateString(file, 'file');
548551
validateArgumentNullCheck(file, 'file');
@@ -592,11 +595,19 @@ function normalizeSpawnArguments(file, args, options) {
592595
}
593596

594597
// Validate the shell, if present.
595-
if (options.shell != null &&
596-
typeof options.shell !== 'boolean' &&
597-
typeof options.shell !== 'string') {
598-
throw new ERR_INVALID_ARG_TYPE('options.shell',
599-
['boolean', 'string'], options.shell);
598+
if (options.shell != null) {
599+
if (typeof options.shell !== 'boolean' && typeof options.shell !== 'string') {
600+
throw new ERR_INVALID_ARG_TYPE('options.shell',
601+
['boolean', 'string'], options.shell);
602+
}
603+
validateArgumentNullCheck(options.shell, 'options.shell');
604+
if (pendingDeprecation && options.shell === '' && !emittedDEP0195Already) {
605+
process.emitWarning('Passing an empty string as the shell option when ' +
606+
'calling child_process functions is deprecated.',
607+
'DeprecationWarning',
608+
'DEP0195');
609+
emittedDEP0195Already = true;
610+
}
600611
}
601612

602613
// Validate argv0, if present.
@@ -618,7 +629,6 @@ function normalizeSpawnArguments(file, args, options) {
618629
}
619630

620631
if (options.shell) {
621-
validateArgumentNullCheck(options.shell, 'options.shell');
622632
if (args.length > 0 && !emittedDEP0190Already) {
623633
process.emitWarning(
624634
'Passing args to a child process with shell option true can lead to security ' +

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
// Flags: --pending-deprecation
12
'use strict';
3+
const common = require('../common');
24
const assert = require('assert');
35
const { exec, execSync } = require('child_process');
46

@@ -10,3 +12,8 @@ const invalidArgTypeError = {
1012
for (const fn of [exec, execSync]) {
1113
assert.throws(() => fn('should-throw-on-boolean-shell-option', { shell: false }), invalidArgTypeError);
1214
}
15+
16+
const deprecationMessage = 'Passing an empty string as the shell option when calling ' +
17+
'child_process functions is deprecated.';
18+
common.expectWarning('DeprecationWarning', deprecationMessage, 'DEP0195');
19+
exec('does-not-exist', { shell: '' }, common.mustCall());

0 commit comments

Comments
 (0)