Skip to content

Incorrect command line argument quoting/escaping in extHostTerminalShellIntegration.ts #308890

@Colengms

Description

@Colengms

Does this issue occur when all extensions are disabled?: Yes

Version: 1.116.0-insider (system setup)
Commit: 1ec46de
Date: 2026-04-09T09:55:17-07:00
Electron: 39.8.5
ElectronBuildId: 13703022
Chromium: 142.0.7444.265
Node.js: 22.22.1
V8: 14.2.231.22-electron.0
OS: Windows_NT x64 10.0.26200

This is regarding the follow suspicious code, which does not appear to properly handle converting an executable/command path and an arguments array (which should always be assumed to not contain quoting/escaping, as that is a 'shell command line' concept), to a shell command line:

// executeCommand(commandLine: string): vscode.TerminalShellExecution;
// executeCommand(executable: string, args: string[]): vscode.TerminalShellExecution;
executeCommand(commandLineOrExecutable: string, args?: string[]): vscode.TerminalShellExecution {
if (!supportsExecuteCommandApi) {
throw new Error('This terminal does not support the executeCommand API.');
}
let commandLineValue = commandLineOrExecutable;
if (args) {
for (const arg of args) {
const wrapInQuotes = !arg.match(/["'`]/) && arg.match(/\s/);
if (wrapInQuotes) {
commandLineValue += ` "${arg}"`;
} else {
commandLineValue += ` ${arg}`;
}
}
}

Some related background:
Everyone quotes command line arguments the wrong way
How Command Line Parameters Are Parsed
CommandLineToArgvW function (shellapi.h)

AI generated bug details:

Bug: TerminalShellIntegration.executeCommand(executable, args) serializes structured input into shell text using naive quoting

Summary

The TerminalShellIntegration.executeCommand(executable, args) overload looks like it accepts structured argv-style input, but the current implementation in src/vs/workbench/api/common/extHostTerminalShellIntegration.ts simply builds a single shell command line string:

let commandLineValue = commandLineOrExecutable;
if (args) {
	for (const arg of args) {
		const wrapInQuotes = !arg.match(/["'`]/) && arg.match(/\s/);
		if (wrapInQuotes) {
			commandLineValue += ` "${arg}"`;
		} else {
			commandLineValue += ` ${arg}`;
		}
	}
}

This means the executable token is copied verbatim and the arguments are appended with only a minimal quoting heuristic before the whole string is reparsed by the active shell.

Problem

This serialization is shell-agnostic and incomplete:

  • the command/executable portion is not quoted or escaped at all,
  • arguments are only quoted in a narrow whitespace-only case,
  • literal quote characters inside arguments are not escaped,
  • shell metacharacters like ;, &, |, <, >, `, and $() are not handled safely,
  • and quoting/escaping rules differ significantly across shells and platforms (zsh, bash, pwsh, cmd, etc.).

As a result, this API overload does not preserve structured input reliably. Both the executable token and the argument boundaries can change when the shell reparses the constructed command line.

Why this matters

An API shaped like:

executeCommand(executable: string, args: string[])

strongly suggests that executable and each item in args are treated as literal tokens. But the current behavior is really “best-effort shell string construction”, which is a weaker and more error-prone contract.

At minimum, arguments containing literal double quotes are mishandled. More broadly, this can lead to incorrect argv at the target process or unexpected shell interpretation.

Relevant code path

  1. InternalTerminalShellIntegration.executeCommand(...) builds commandLineValue
  2. MainThreadTerminalShellIntegration.$executeCommand(...) forwards the string
  3. TerminalInstance.runCommand(...) / sendText(...) writes it into the PTY
  4. the active shell reparses that text and launches the subprocess

So this API does not directly spawn a process from an argument array; it sends a command line string to the shell.

Example problematic inputs

These should be treated as literal tokens, but may be misparsed by the shell:

  • executable: '/tmp/My Tool'
  • args: ['a"b']
  • args: ['a b"c']
  • args: ['foo;bar']
  • args: ['$(uname)']

Expected behavior

Either:

  1. the executeCommand(executable, args) overload should use shell/platform-specific escaping that preserves the executable token and argument boundaries as accurately as possible for the active shell,

or

  1. the API/docs should explicitly state that this overload is only a best-effort conversion to shell text and cannot guarantee exact argv preservation.

At minimum, literal quotes inside arguments should be escaped correctly, and the executable token should not be emitted verbatim when it requires quoting.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions