Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 33 additions & 10 deletions src/lib/utils/subprocess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,28 @@
shell?: boolean;
}

/**
* When shell mode is enabled, merge args into the command string so that
* Node.js does not receive both a non-empty args array and `shell: true`.
* Passing both triggers DEP0190 on Node ≥ 22 (and a warning on earlier
* versions) because the arguments are concatenated without escaping.
*/
function resolveCommand(command: string, args: string[], useShell: boolean): { cmd: string; cmdArgs: string[] } {
if (useShell) {
return { cmd: [command, ...args].join(' '), cmdArgs: [] };
}
return { cmd: command, cmdArgs: args };
}

export async function runSubprocess(command: string, args: string[], options: SubprocessOptions = {}): Promise<void> {
const shell = options.shell ?? isWindows;
const { cmd, cmdArgs } = resolveCommand(command, args, shell);
return new Promise((resolve, reject) => {
const child = spawn(command, args, {
const child = spawn(cmd, cmdArgs, {
cwd: options.cwd,
env: options.env,
stdio: options.stdio ?? 'inherit',
shell: options.shell ?? isWindows,
shell,
});

child.on('error', reject);
Expand All @@ -46,12 +61,14 @@
args: string[],
options: SubprocessOptions = {}
): Promise<boolean> {
const shell = options.shell ?? isWindows;
const { cmd, cmdArgs } = resolveCommand(command, args, shell);
return new Promise(resolve => {
const child = spawn(command, args, {
const child = spawn(cmd, cmdArgs, {
cwd: options.cwd,
env: options.env,
stdio: options.stdio ?? 'ignore',
shell: options.shell ?? isWindows,
shell,
});

child.on('error', () => resolve(false));
Expand All @@ -71,13 +88,15 @@
args: string[],
options: SubprocessOptions = {}
): Promise<SubprocessResult> {
const shell = options.shell ?? isWindows;
const { cmd, cmdArgs } = resolveCommand(command, args, shell);
return new Promise(resolve => {
const child = spawn(command, args, {
const child = spawn(cmd, cmdArgs, {
cwd: options.cwd,
env: options.env,
stdio: 'pipe',
shell: options.shell ?? isWindows,
shell,
});

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium

This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.

let stdout = '';
let stderr = '';
Expand Down Expand Up @@ -105,13 +124,15 @@
args: string[],
options: SubprocessOptions = {}
): SubprocessResult {
const result = spawnSync(command, args, {
const shell = options.shell ?? isWindows;
const { cmd, cmdArgs } = resolveCommand(command, args, shell);
const result = spawnSync(cmd, cmdArgs, {
cwd: options.cwd,
env: options.env,
stdio: 'pipe',
shell: options.shell ?? isWindows,
shell,
encoding: 'utf-8',
});

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium

This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.

return {
stdout: result.stdout ?? '',
Expand All @@ -122,12 +143,14 @@
}

export function checkSubprocessSync(command: string, args: string[], options: SubprocessOptions = {}): boolean {
const shell = options.shell ?? isWindows;
const { cmd, cmdArgs } = resolveCommand(command, args, shell);
try {
const result = spawnSync(command, args, {
const result = spawnSync(cmd, cmdArgs, {
cwd: options.cwd,
env: options.env,
stdio: options.stdio ?? 'ignore',
shell: options.shell ?? isWindows,
shell,
});
return result.status === 0;
} catch {
Expand Down
Loading