CVE-2023-40185 (published 2023-08-23T21:15Z)

shescape is simple shell escape library for JavaScript. This may impact users that use Shescape on Windows in a threaded context. The vulnerability can result in Shescape escaping (or quoting) for the wrong shell, thus allowing attackers to bypass protections depending on the combination of expected and used shell. This bug has been patched in version 1.7.4.

CVSS v3 base score: 8.6, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N)

Weakness information:
cwe_id: CWE-150
cwe_name: Improper Neutralization of Escape, Meta, or Control Sequences
description: The product receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as escape, meta, or control character sequences when they are sent to a downstream component.
extended_description: As data is parsed, an injected/absent/malformed delimiter may cause the process to take unexpected actions.
         

Fix information:
repo_url: https://github.com/ericcornelissen/shescape
commit_details: repo_url: https://github.com/ericcornelissen/shescape
author: Eric Cornelissen
author_date: 2023-08-21 10:05:50+02:00
msg: Provide explicit `$PATH` value to which (#1142)

Update the implementation of `resolveExecutable` to accept the
environment variables so that they can explicitly be provided to
which [1]. All internal code and tests have been updated accordingly,
no external changes.

This is in an effort to fix a problem where environment variables aren't
always passed on correctly to subprocesses. The problem is described in
[2], though if Shescape is used in a forked process on Windows it would
face the same problem. (As a problem found through the test runner, use
of which [1] in tests has been updated similarly.

As a result of this change, the integration and e2e test on Windows are
quite a bit slower (because now they actually look up the executable).
Hence, both of these suites have been updated to use separated test
files in order to better leverage AVA's concurrency - speeding up
individual files and avoiding timeouts as well as running tests quicker
if cores are available.

This change also allowed for including executables without a file
extension in the integration and e2e tests, further improving coverage.
(This replaced the `.EXE` coverage as extensionless executables are
resolved to `.EXE`, thus reducing test count without reducing coverage.)

As a side note, it was funny to see that `getShellName` name unit tests
for both Unix and Windows already generated and provided environment
variables (:

--
1. https://www.npmjs.com/package/which
2. https://github.com/avajs/ava/issues/3014
repository: repo_url: https://github.com/ericcornelissen/shescape
repo_name: ericcornelissen/shescape
description: Simple shell escape library for JavaScript
repo_language: JavaScript
owner: ericcornelissen
file_changes: filename: CHANGELOG.md
change_type: ModificationType.MODIFY
diff: @@ -7,6 +7,7 @@ Versioning].
 
 ## [Unreleased]
 
+- Fix potential silent executable lookup failure for Windows. ([#1142])
 - Support more valid `shell` values for Windows. ([#1137])
 
 ## [1.7.3] - 2023-08-07
@@ -292,6 +293,7 @@ Versioning].
 [#1083]: https://github.com/ericcornelissen/shescape/pull/1083
 [#1094]: https://github.com/ericcornelissen/shescape/pull/1094
 [#1137]: https://github.com/ericcornelissen/shescape/pull/1137
+[#1142]: https://github.com/ericcornelissen/shescape/pull/1142
 [552e8ea]: https://github.com/ericcornelissen/shescape/commit/552e8eab56861720b1d4e5474fb65741643358f9
 [keep a changelog]: https://keepachangelog.com/en/1.0.0/
 [semantic versioning]: https://semver.org/spec/v2.0.0.html

programming_language: Markdown
filename: index.js
change_type: ModificationType.MODIFY
diff: @@ -53,7 +53,7 @@ function getPlatformHelpers() {
 export function escape(arg, options = {}) {
   const helpers = getPlatformHelpers();
   const { flagProtection, interpolation, shellName } = parseOptions(
-    { options, process },
+    { env: process.env, options },
     helpers,
   );
   const argAsString = checkedToString(arg);
@@ -129,7 +129,7 @@ export function escapeAll(args, options = {}) {
 export function quote(arg, options = {}) {
   const helpers = getPlatformHelpers();
   const { flagProtection, shellName } = parseOptions(
-    { options, process },
+    { env: process.env, options },
     helpers,
   );
   const argAsString = checkedToString(arg);

programming_language: TypeScript
filename: executables.js
change_type: ModificationType.MODIFY
diff: @@ -12,6 +12,7 @@
  * - Follows symbolic links.
  *
  * @param {object} args The arguments for this function.
+ * @param {object} args.env The environment variables.
  * @param {string} args.executable A string representation of the executable.
  * @param {object} deps The dependencies for this function.
  * @param {Function} deps.exists A function to check if a file exists.
@@ -19,9 +20,12 @@
  * @param {Function} deps.which A function to perform a `which(1)`-like lookup.
  * @returns {string} The full path to the binary of the executable.
  */
-export function resolveExecutable({ executable }, { exists, readlink, which }) {
+export function resolveExecutable(
+  { env, executable },
+  { exists, readlink, which },
+) {
   try {
-    executable = which(executable);
+    executable = which(executable, { path: env.PATH || env.Path });
   } catch (_) {
     // For backwards compatibility return the executable even if its location
     // cannot be obtained

programming_language: TypeScript
filename: options.js
change_type: ModificationType.MODIFY
diff: @@ -10,25 +10,24 @@ import { isString } from "./reflection.js";
  * Parses options provided to shescape.
  *
  * @param {object} args The arguments for this function.
+ * @param {object} args.env The environment variables.
  * @param {object} args.options The options for escaping.
  * @param {boolean} [args.options.flagProtection] Is flag protection enabled.
  * @param {boolean} [args.options.interpolation] Is interpolation enabled.
  * @param {boolean | string} [args.options.shell] The shell to escape for.
- * @param {object} args.process The `process` values.
- * @param {object} args.process.env The environment variables.
  * @param {object} deps The dependencies for this function.
  * @param {Function} deps.getDefaultShell Function to get the default shell.
  * @param {Function} deps.getShellName Function to get the name of a shell.
  * @returns {object} The parsed arguments.
  */
 export function parseOptions(
-  { options: { flagProtection, interpolation, shell }, process: { env } },
+  { env, options: { flagProtection, interpolation, shell } },
   { getDefaultShell, getShellName },
 ) {
   flagProtection = flagProtection ? true : false;
   interpolation = interpolation ? true : false;
   shell = isString(shell) ? shell : getDefaultShell({ env });
 
-  const shellName = getShellName({ shell }, { resolveExecutable });
+  const shellName = getShellName({ env, shell }, { resolveExecutable });
   return { flagProtection, interpolation, shellName };
 }

programming_language: TypeScript
filename: unix.js
change_type: ModificationType.MODIFY
diff: @@ -121,14 +121,15 @@ export function getFlagProtectionFunction(shellName) {
  * Determines the name of the shell identified by a file path or file name.
  *
  * @param {object} args The arguments for this function.
+ * @param {object} args.env The environment variables.
  * @param {string} args.shell The name or path of the shell.
  * @param {object} deps The dependencies for this function.
  * @param {Function} deps.resolveExecutable Resolve the path to an executable.
  * @returns {string} The shell name.
  */
-export function getShellName({ shell }, { resolveExecutable }) {
+export function getShellName({ env, shell }, { resolveExecutable }) {
   shell = resolveExecutable(
-    { executable: shell },
+    { env, executable: shell },
     { exists: fs.existsSync, readlink: fs.readlinkSync, which: which.sync },
   );
 

programming_language: TypeScript
filename: win.js
change_type: ModificationType.MODIFY
diff: @@ -98,14 +98,15 @@ export function getFlagProtectionFunction(shellName) {
  * Determines the name of the shell identified by a file path or file name.
  *
  * @param {object} args The arguments for this function.
+ * @param {object} args.env The environment variables.
  * @param {string} args.shell The name or path of the shell.
  * @param {object} deps The dependencies for this function.
  * @param {Function} deps.resolveExecutable Resolve the path to an executable.
  * @returns {string} The shell name.
  */
-export function getShellName({ shell }, { resolveExecutable }) {
+export function getShellName({ env, shell }, { resolveExecutable }) {
   shell = resolveExecutable(
-    { executable: shell },
+    { env, executable: shell },
     { exists: fs.existsSync, readlink: fs.readlinkSync, which: which.sync },
   );
 

programming_language: JavaScript
filename: _constants.cjs
change_type: ModificationType.MODIFY
diff: @@ -60,11 +60,13 @@ module.exports.shellsUnix = [
 
 /* Windows related constants */
 module.exports.binCmd = "cmd.exe";
+module.exports.binCmdNoExt = "cmd";
 module.exports.binPowerShell = "powershell.exe";
+module.exports.binPowerShellNoExt = "powershell";
 
 module.exports.shellsWindows = [
   module.exports.binCmd,
-  "cmd.EXE",
+  module.exports.binCmdNoExt,
   module.exports.binPowerShell,
-  "powershell.EXE",
+  module.exports.binPowerShellNoExt,
 ];

programming_language: CoffeeScript
filename: _.js
change_type: ModificationType.MODIFY
diff: @@ -3,8 +3,8 @@
  * @license MIT
  */
 
-import { injectionStrings } from "../../testing.js";
 import * as constants from "../_constants.cjs";
+import * as common from "./_common.js";
 import * as macros from "./_macros.js";
 
-export { constants, macros, injectionStrings };
+export { common, constants, macros };

programming_language: TypeScript
filename: _common.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,53 @@
+/**
+ * @overview Provides common utilities for end-to-end tests.
+ * @license MIT
+ */
+
+import process from "node:process";
+
+import test from "ava";
+import isCI from "is-ci";
+import which from "which";
+
+import { injectionStrings } from "../../testing.js";
+import * as constants from "../_constants.cjs";
+
+/**
+ * Get a list of strings to use as arguments in end-to-end tests.
+ *
+ * @returns {string[]} A list of test arguments.
+ */
+export function getTestArgs() {
+  return ["harmless", ...injectionStrings];
+}
+
+/**
+ * Get the AVA test function to use for the given shell.
+ *
+ * @param {string} shell The shell to run a test for.
+ * @returns {Function} An AVA `test` function.
+ */
+export function getTestFn(shell) {
+  try {
+    if (!isCI && typeof shell === "string") {
+      which.sync(shell, { path: process.env.PATH || process.env.Path });
+    }
+
+    return test;
+  } catch (_) {
+    return test.skip;
+  }
+}
+
+/**
+ * Get a list of `shell` option values to use in end-to-end tests.
+ *
+ * @returns {(boolean | string)[]} A list of `shell` option values.
+ */
+export function getTestShells() {
+  const systemShells = constants.isWindows
+    ? constants.shellsWindows
+    : constants.shellsUnix;
+
+  return [false, true, ...systemShells];
+}

programming_language: TypeScript
filename: child_process.test.js
change_type: ModificationType.DELETE
diff: @@ -1,40 +0,0 @@
-/**
- * @overview Contains end-to-end tests of using Shescape with the child_process
- * package.
- * @license MIT
- */
-
-import test from "ava";
-import isCI from "is-ci";
-import which from "which";
-
-import { constants, macros, injectionStrings } from "./_.js";
-
-const systemShells = constants.isWindows
-  ? constants.shellsWindows
-  : constants.shellsUnix;
-
-const testArgs = ["harmless", ...injectionStrings];
-const testShells = [false, true, ...systemShells];
-
-for (const arg of testArgs) {
-  test(macros.fork, { arg });
-
-  for (const shell of testShells) {
-    let runTest = test;
-    try {
-      if (!isCI && typeof shell === "string") {
-        which.sync(shell);
-      }
-    } catch (_) {
-      runTest = test.skip;
-    }
-
-    runTest(macros.exec, { arg, shell });
-    runTest(macros.execSync, { arg, shell });
-    runTest(macros.execFile, { arg, shell });
-    runTest(macros.execFileSync, { arg, shell });
-    runTest(macros.spawn, { arg, shell });
-    runTest(macros.spawnSync, { arg, shell });
-  }
-}

programming_language: unknown
filename: exec-file.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,15 @@
+/**
+ * @overview Contains end-to-end tests of using Shescape with the child_process
+ * package's `execFile` (and `execFileSync`) functions.
+ * @license MIT
+ */
+
+import { common, macros } from "./_.js";
+
+for (const shell of common.getTestShells()) {
+  const test = common.getTestFn(shell);
+  for (const arg of common.getTestArgs()) {
+    test(macros.execFile, { arg, shell });
+    test(macros.execFileSync, { arg, shell });
+  }
+}

programming_language: TypeScript
filename: exec.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,15 @@
+/**
+ * @overview Contains end-to-end tests of using Shescape with the child_process
+ * package's `exec` (and `execSync`) functions.
+ * @license MIT
+ */
+
+import { common, macros } from "./_.js";
+
+for (const shell of common.getTestShells()) {
+  const test = common.getTestFn(shell);
+  for (const arg of common.getTestArgs()) {
+    test(macros.exec, { arg, shell });
+    test(macros.execSync, { arg, shell });
+  }
+}

programming_language: TypeScript
filename: fork.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,12 @@
+/**
+ * @overview Contains end-to-end tests of using Shescape with the child_process
+ * package's `fork` functions.
+ * @license MIT
+ */
+
+import { common, macros } from "./_.js";
+
+for (const arg of common.getTestArgs()) {
+  const test = common.getTestFn(null);
+  test(macros.fork, { arg });
+}

programming_language: TypeScript
filename: spawn.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,15 @@
+/**
+ * @overview Contains end-to-end tests of using Shescape with the child_process
+ * package's `spawn` (and `spawnSync`) functions.
+ * @license MIT
+ */
+
+import { common, macros } from "./_.js";
+
+for (const shell of common.getTestShells()) {
+  const test = common.getTestFn(shell);
+  for (const arg of common.getTestArgs()) {
+    test(macros.spawn, { arg, shell });
+    test(macros.spawnSync, { arg, shell });
+  }
+}

programming_language: TypeScript
filename: _generators.js
change_type: ModificationType.MODIFY
diff: @@ -5,20 +5,8 @@
 
 import * as fixturesUnix from "../fixtures/unix.js";
 import * as fixturesWindows from "../fixtures/win.js";
-import common from "../_constants.cjs";
 
-/**
- * Returns the shells officially supported by Shescape for the current platform.
- *
- * @yields {string} Supported shells for the current platform.
- */
-export function* platformShells() {
-  if (common.isWindows) {
-    yield* common.shellsWindows;
-  } else {
-    yield* common.shellsUnix;
-  }
-}
+import { constants } from "./_.js";
 
 /**
  * Returns the test fixtures for the current platform.
@@ -26,7 +14,7 @@ export function* platformShells() {
  * @returns {object} All test fixtures for the current platform.
  */
 function getPlatformFixtures() {
-  if (common.isWindows) {
+  if (constants.isWindows) {
     return fixturesWindows;
   } else {
     return fixturesUnix;
@@ -40,13 +28,16 @@ function getPlatformFixtures() {
  * @returns {object} All test fixtures for `shell`.
  */
 function getShellFixtures(shell) {
-  shell = shell.toLowerCase();
+  let shellName = shell.toLowerCase();
+  if (constants.isWindows) {
+    shellName = shellName.endsWith(".exe") ? shellName : `${shellName}.exe`;
+  }
 
   const fixtures = getPlatformFixtures();
   return {
-    escape: Object.values(fixtures.escape[shell]).flat(),
-    flag: Object.values(fixtures.flag[shell]).flat(),
-    quote: Object.values(fixtures.quote[shell]).flat(),
+    escape: Object.values(fixtures.escape[shellName]).flat(),
+    flag: Object.values(fixtures.flag[shellName]).flat(),
+    quote: Object.values(fixtures.quote[shellName]).flat(),
   };
 }
 

programming_language: TypeScript
filename: bash.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escapeAll` for the
+ * Bourne-again shell (Bash).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escapeAll } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binBash}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binBash)) {
+    const { expected, input, options } = scenario;
+    const result = escapeAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});

programming_language: TypeScript
filename: cmd-exe.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escapeAll` for the Windows
+ * Command Prompt (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escapeAll } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binCmd}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binCmd)) {
+    const { expected, input, options } = scenario;
+    const result = escapeAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});

programming_language: TypeScript
filename: cmd.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escapeAll` for the Windows
+ * Command Prompt (without extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escapeAll } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binCmdNoExt}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binCmdNoExt)) {
+    const { expected, input, options } = scenario;
+    const result = escapeAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});

programming_language: TypeScript
filename: commonjs.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,23 @@
+/**
+ * @overview Contains integration test for the CommonJS version of
+ * `shescape.escapeAll`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+import * as fc from "fast-check";
+
+import { arbitrary } from "../_.js";
+
+import { escapeAll } from "shescape";
+import { escapeAll as escapeAllCjs } from "../../../index.cjs";
+
+testProp(
+  "esm === cjs",
+  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
+  (t, args, options) => {
+    const resultEsm = escapeAll(args, options);
+    const resultCjs = escapeAllCjs(args, options);
+    t.deepEqual(resultEsm, resultCjs);
+  },
+);

programming_language: TypeScript
filename: csh.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escapeAll` for the C shell
+ * (csh).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escapeAll } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binCsh}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binCsh)) {
+    const { expected, input, options } = scenario;
+    const result = escapeAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});

programming_language: TypeScript
filename: dash.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escapeAll` for the Debian
+ * Almquist shell (Dash).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escapeAll } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binDash}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binDash)) {
+    const { expected, input, options } = scenario;
+    const result = escapeAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});

programming_language: TypeScript
filename: invalid.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,22 @@
+/**
+ * @overview Contains integration tests for invalid use of `shescape.escapeAll`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+import test from "ava";
+
+import { arbitrary, constants, macros } from "../_.js";
+
+import { escapeAll } from "shescape";
+
+testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
+  for (const { value } of constants.illegalArguments) {
+    t.throws(() => escapeAll([value], options), { instanceOf: TypeError });
+    t.throws(() => escapeAll(value, options), { instanceOf: TypeError });
+  }
+});
+
+test(macros.prototypePollution, (_, payload) => {
+  escapeAll(["a"], payload);
+});

programming_language: TypeScript
filename: powershell-exe.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escapeAll` for Windows
+ * PowerShell (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escapeAll } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binPowerShell}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binPowerShell)) {
+    const { expected, input, options } = scenario;
+    const result = escapeAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});

programming_language: TypeScript
filename: powershell.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,23 @@
+/**
+ * @overview Contains integration tests for `shescape.escapeAll` for Windows
+ * PowerShell (without extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escapeAll } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binPowerShellNoExt}`, (t) => {
+  for (const scenario of generate.escapeExamples(
+    constants.binPowerShellNoExt,
+  )) {
+    const { expected, input, options } = scenario;
+    const result = escapeAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});

programming_language: TypeScript
filename: valid.test.js
change_type: ModificationType.RENAME
diff: @@ -1,25 +1,14 @@
 /**
- * @overview Contains integration tests for `shescape.escapeAll`.
+ * @overview Contains integration tests for valid use of `shescape.escapeAll`.
  * @license MIT
  */
 
 import { testProp } from "@fast-check/ava";
-import test from "ava";
 import * as fc from "fast-check";
 
-import { arbitrary, constants, generate, macros } from "./_.js";
+import { arbitrary } from "../_.js";
 
 import { escape, escapeAll } from "shescape";
-import { escapeAll as escapeAllCjs } from "../../index.cjs";
-
-for (const shell of generate.platformShells()) {
-  test(`inputs are escaped for ${shell}`, (t) => {
-    for (const { expected, input, options } of generate.escapeExamples(shell)) {
-      const result = escapeAll([input], options);
-      t.deepEqual(result, [expected]);
-    }
-  });
-}
 
 testProp(
   "return values",
@@ -71,24 +60,3 @@ testProp(
     t.is(entry, escape(arg, options));
   },
 );
-
-testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
-  for (const { value } of constants.illegalArguments) {
-    t.throws(() => escapeAll([value], options), { instanceOf: TypeError });
-    t.throws(() => escapeAll(value, options), { instanceOf: TypeError });
-  }
-});
-
-test(macros.prototypePollution, (_, payload) => {
-  escapeAll(["a"], payload);
-});
-
-testProp(
-  "esm === cjs",
-  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
-  (t, args, options) => {
-    const resultEsm = escapeAll(args, options);
-    const resultCjs = escapeAllCjs(args, options);
-    t.deepEqual(resultEsm, resultCjs);
-  },
-);

programming_language: Scala
filename: zsh.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escapeAll` for the Z shell
+ * (Zsh).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escapeAll } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binZsh}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binZsh)) {
+    const { expected, input, options } = scenario;
+    const result = escapeAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});

programming_language: TypeScript
filename: escape.test.js
change_type: ModificationType.DELETE
diff: @@ -1,50 +0,0 @@
-/**
- * @overview Contains integration tests for `shescape.escape`.
- * @license MIT
- */
-
-import { testProp } from "@fast-check/ava";
-import test from "ava";
-
-import { arbitrary, constants, generate, macros } from "./_.js";
-
-import { escape as escape } from "shescape";
-import { escape as escapeCjs } from "../../index.cjs";
-
-for (const shell of generate.platformShells()) {
-  test(`input is escaped for ${shell}`, (t) => {
-    for (const { expected, input, options } of generate.escapeExamples(shell)) {
-      const result = escape(input, options);
-      t.is(result, expected);
-    }
-  });
-}
-
-testProp(
-  "return values",
-  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
-  (t, arg, options) => {
-    const result = escape(arg, options);
-    t.is(typeof result, "string");
-  },
-);
-
-testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
-  for (const { value } of constants.illegalArguments) {
-    t.throws(() => escape(value, options), { instanceOf: TypeError });
-  }
-});
-
-test(macros.prototypePollution, (_, payload) => {
-  escape("a", payload);
-});
-
-testProp(
-  "esm === cjs",
-  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
-  (t, arg, options) => {
-    const resultEsm = escape(arg, options);
-    const resultCjs = escapeCjs(arg, options);
-    t.is(resultEsm, resultCjs);
-  },
-);

programming_language: unknown
filename: bash.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quote` for the
+ * Bourne-again shell (Bash).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quote } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binBash}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binBash)) {
+    const { expected, input, options } = scenario;
+    const result = quote(input, options);
+    t.is(result, expected);
+  }
+});

programming_language: TypeScript
filename: cmd-exe.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escape` for the Windows
+ * Command Prompt (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escape } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binCmd}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binCmd)) {
+    const { expected, input, options } = scenario;
+    const result = escape(input, options);
+    t.is(result, expected);
+  }
+});

programming_language: TypeScript
filename: cmd.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escape` for the Windows
+ * Command Prompt (without extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escape } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binCmdNoExt}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binCmdNoExt)) {
+    const { expected, input, options } = scenario;
+    const result = escape(input, options);
+    t.is(result, expected);
+  }
+});

programming_language: TypeScript
filename: commonjs.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,22 @@
+/**
+ * @overview Contains integration tests for the CommonJS version of
+ * `shescape.escape`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+
+import { arbitrary } from "../_.js";
+
+import { escape } from "shescape";
+import { escape as escapeCjs } from "../../../index.cjs";
+
+testProp(
+  "esm === cjs",
+  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
+  (t, arg, options) => {
+    const resultEsm = escape(arg, options);
+    const resultCjs = escapeCjs(arg, options);
+    t.is(resultEsm, resultCjs);
+  },
+);

programming_language: TypeScript
filename: csh.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escape` for the C shell
+ * (csh).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escape } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binCsh}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binCsh)) {
+    const { expected, input, options } = scenario;
+    const result = escape(input, options);
+    t.is(result, expected);
+  }
+});

programming_language: TypeScript
filename: dash.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escape` for the Debian
+ * Almquist shell (Dash).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escape } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binDash}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binDash)) {
+    const { expected, input, options } = scenario;
+    const result = escape(input, options);
+    t.is(result, expected);
+  }
+});

programming_language: TypeScript
filename: invalid.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for invalid use of `shescape.escape`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+import test from "ava";
+
+import { arbitrary, constants, macros } from "../_.js";
+
+import { escape } from "shescape";
+
+testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
+  for (const { value } of constants.illegalArguments) {
+    t.throws(() => escape(value, options), { instanceOf: TypeError });
+  }
+});
+
+test(macros.prototypePollution, (_, payload) => {
+  escape("a", payload);
+});

programming_language: TypeScript
filename: powershell-exe.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escape` for Windows
+ * PowerShell (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escape } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binPowerShell}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binPowerShell)) {
+    const { expected, input, options } = scenario;
+    const result = escape(input, options);
+    t.is(result, expected);
+  }
+});

programming_language: TypeScript
filename: powershell.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,23 @@
+/**
+ * @overview Contains integration tests for `shescape.escape` for Windows
+ * PowerShell (without extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escape } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binPowerShellNoExt}`, (t) => {
+  for (const scenario of generate.escapeExamples(
+    constants.binPowerShellNoExt,
+  )) {
+    const { expected, input, options } = scenario;
+    const result = escape(input, options);
+    t.is(result, expected);
+  }
+});

programming_language: TypeScript
filename: valid.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,19 @@
+/**
+ * @overview Contains integration tests for valid use of `shescape.escape`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+
+import { arbitrary } from "../_.js";
+
+import { escape } from "shescape";
+
+testProp(
+  "return values",
+  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
+  (t, arg, options) => {
+    const result = escape(arg, options);
+    t.is(typeof result, "string");
+  },
+);

programming_language: TypeScript
filename: zsh.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escape` for the Z shell
+ * (Zsh).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escape } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binZsh}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binZsh)) {
+    const { expected, input, options } = scenario;
+    const result = escape(input, options);
+    t.is(result, expected);
+  }
+});

programming_language: TypeScript
filename: bash.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quoteAll` for the
+ * Bourne-again shell (Bash).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quoteAll } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binBash}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binBash)) {
+    const { expected, input, options } = scenario;
+    const result = quoteAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});

programming_language: TypeScript
filename: cmd-exe.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quoteAll` for the Windows
+ * Command Prompt (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quoteAll } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binCmd}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binCmd)) {
+    const { expected, input, options } = scenario;
+    const result = quoteAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});

programming_language: TypeScript
filename: cmd.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quoteAll` for the Windows
+ * Command Prompt (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quoteAll } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binCmdNoExt}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binCmdNoExt)) {
+    const { expected, input, options } = scenario;
+    const result = quoteAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});

programming_language: TypeScript
filename: commonjs.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,23 @@
+/**
+ * @overview Contains integration test for the CommonJS version of
+ * `shescape.quoteAll`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+import * as fc from "fast-check";
+
+import { arbitrary } from "../_.js";
+
+import { quoteAll } from "shescape";
+import { quoteAll as quoteAllCjs } from "../../../index.cjs";
+
+testProp(
+  "esm === cjs",
+  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
+  (t, args, options) => {
+    const resultEsm = quoteAll(args, options);
+    const resultCjs = quoteAllCjs(args, options);
+    t.deepEqual(resultEsm, resultCjs);
+  },
+);

programming_language: JavaScript
filename: csh.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quoteAll` for the C shell
+ * (csh).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quoteAll } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binCsh}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binCsh)) {
+    const { expected, input, options } = scenario;
+    const result = quoteAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});

programming_language: TypeScript
filename: dash.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quoteAll` for the Debian
+ * Almquist shell (Dash).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quoteAll } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binDash}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binDash)) {
+    const { expected, input, options } = scenario;
+    const result = quoteAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});

programming_language: TypeScript
filename: invalid.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,22 @@
+/**
+ * @overview Contains integration tests for invalid use of `shescape.quote`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+import test from "ava";
+
+import { arbitrary, constants, macros } from "../_.js";
+
+import { quoteAll } from "shescape";
+
+testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
+  for (const { value } of constants.illegalArguments) {
+    t.throws(() => quoteAll([value], options), { instanceOf: TypeError });
+    t.throws(() => quoteAll(value, options), { instanceOf: TypeError });
+  }
+});
+
+test(macros.prototypePollution, (_, payload) => {
+  quoteAll(["a"], payload);
+});

programming_language: TypeScript
filename: powershell-exe.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quoteAll` for Windows
+ * PowerShell (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quoteAll } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binPowerShell}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binPowerShell)) {
+    const { expected, input, options } = scenario;
+    const result = quoteAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});

programming_language: TypeScript
filename: powershell.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quoteAll` for Windows
+ * PowerShell (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quoteAll } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binPowerShellNoExt}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binPowerShellNoExt)) {
+    const { expected, input, options } = scenario;
+    const result = quoteAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});

programming_language: TypeScript
filename: valid.test.js
change_type: ModificationType.RENAME
diff: @@ -1,25 +1,14 @@
 /**
- * @overview Contains integration tests for `shescape.quoteAll`.
+ * @overview Contains integration tests for valid use of `shescape.quoteAll`.
  * @license MIT
  */
 
 import { testProp } from "@fast-check/ava";
-import test from "ava";
 import * as fc from "fast-check";
 
-import { arbitrary, constants, generate, macros } from "./_.js";
+import { arbitrary } from "../_.js";
 
 import { quote, quoteAll as quoteAll } from "shescape";
-import { quoteAll as quoteAllCjs } from "../../index.cjs";
-
-for (const shell of generate.platformShells()) {
-  test(`inputs are quoted for ${shell}`, (t) => {
-    for (const { expected, input, options } of generate.quoteExamples(shell)) {
-      const result = quoteAll([input], options);
-      t.deepEqual(result, [expected]);
-    }
-  });
-}
 
 testProp(
   "return values",
@@ -71,24 +60,3 @@ testProp(
     t.is(entry, quote(arg, options));
   },
 );
-
-testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
-  for (const { value } of constants.illegalArguments) {
-    t.throws(() => quoteAll([value], options), { instanceOf: TypeError });
-    t.throws(() => quoteAll(value, options), { instanceOf: TypeError });
-  }
-});
-
-test(macros.prototypePollution, (_, payload) => {
-  quoteAll(["a"], payload);
-});
-
-testProp(
-  "esm === cjs",
-  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
-  (t, args, options) => {
-    const resultEsm = quoteAll(args, options);
-    const resultCjs = quoteAllCjs(args, options);
-    t.deepEqual(resultEsm, resultCjs);
-  },
-);

programming_language: JavaScript
filename: zsh.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quoteAll` for the Z shell
+ * (Zsh).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quoteAll } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binZsh}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binZsh)) {
+    const { expected, input, options } = scenario;
+    const result = quoteAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});

programming_language: TypeScript
filename: quote.test.js
change_type: ModificationType.DELETE
diff: @@ -1,50 +0,0 @@
-/**
- * @overview Contains integration tests for `shescape.quote`.
- * @license MIT
- */
-
-import { testProp } from "@fast-check/ava";
-import test from "ava";
-
-import { arbitrary, constants, generate, macros } from "./_.js";
-
-import { quote as quote } from "shescape";
-import { quote as quoteCjs } from "../../index.cjs";
-
-for (const shell of generate.platformShells()) {
-  test(`input is quoted for ${shell}`, (t) => {
-    for (const { expected, input, options } of generate.quoteExamples(shell)) {
-      const result = quote(input, options);
-      t.is(result, expected);
-    }
-  });
-}
-
-testProp(
-  "return value",
-  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
-  (t, arg, options) => {
-    const result = quote(arg, options);
-    t.is(typeof result, "string");
-  },
-);
-
-testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
-  for (const { value } of constants.illegalArguments) {
-    t.throws(() => quote(value, options), { instanceOf: TypeError });
-  }
-});
-
-test(macros.prototypePollution, (_, payload) => {
-  quote("a", payload);
-});
-
-testProp(
-  "esm === cjs",
-  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
-  (t, arg, options) => {
-    const resultEsm = quote(arg, options);
-    const resultCjs = quoteCjs(arg, options);
-    t.is(resultEsm, resultCjs);
-  },
-);

programming_language: unknown
filename: bash.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escape` for the
+ * Bourne-again shell (Bash).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escape } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binBash}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binBash)) {
+    const { expected, input, options } = scenario;
+    const result = escape(input, options);
+    t.is(result, expected);
+  }
+});

programming_language: TypeScript
filename: cmd-exe.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quote` for the Windows
+ * Command Prompt (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quote } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binCmd}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binCmd)) {
+    const { expected, input, options } = scenario;
+    const result = quote(input, options);
+    t.is(result, expected);
+  }
+});

programming_language: TypeScript
filename: cmd.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quote` for the Windows
+ * Command Prompt (without extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quote } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binCmdNoExt}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binCmdNoExt)) {
+    const { expected, input, options } = scenario;
+    const result = quote(input, options);
+    t.is(result, expected);
+  }
+});

programming_language: TypeScript
filename: commonjs.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,22 @@
+/**
+ * @overview Contains integration tests for the CommonJS version of
+ * `shescape.quote`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+
+import { arbitrary } from "../_.js";
+
+import { quote } from "shescape";
+import { quote as quoteCjs } from "../../../index.cjs";
+
+testProp(
+  "esm === cjs",
+  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
+  (t, arg, options) => {
+    const resultEsm = quote(arg, options);
+    const resultCjs = quoteCjs(arg, options);
+    t.is(resultEsm, resultCjs);
+  },
+);

programming_language: JavaScript
filename: csh.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quote` for the C shell
+ * (csh).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quote } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binCsh}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binCsh)) {
+    const { expected, input, options } = scenario;
+    const result = quote(input, options);
+    t.is(result, expected);
+  }
+});

programming_language: TypeScript
filename: dash.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quote` for the Debian
+ * Almquist shell (Dash).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quote } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binDash}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binDash)) {
+    const { expected, input, options } = scenario;
+    const result = quote(input, options);
+    t.is(result, expected);
+  }
+});

programming_language: TypeScript
filename: invalid.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for invalid use of `shescape.quote`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+import test from "ava";
+
+import { arbitrary, constants, macros } from "../_.js";
+
+import { quote } from "shescape";
+
+testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
+  for (const { value } of constants.illegalArguments) {
+    t.throws(() => quote(value, options), { instanceOf: TypeError });
+  }
+});
+
+test(macros.prototypePollution, (_, payload) => {
+  quote("a", payload);
+});

programming_language: TypeScript
filename: powershell-exe.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quote` for Windows
+ * PowerShell (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quote } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binPowerShell}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binPowerShell)) {
+    const { expected, input, options } = scenario;
+    const result = quote(input, options);
+    t.is(result, expected);
+  }
+});

programming_language: TypeScript
filename: powershell.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quote` for Windows
+ * PowerShell (without extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quote } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binPowerShellNoExt}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binPowerShellNoExt)) {
+    const { expected, input, options } = scenario;
+    const result = quote(input, options);
+    t.is(result, expected);
+  }
+});

programming_language: TypeScript
filename: valid.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,19 @@
+/**
+ * @overview Contains integration tests for valid use of `shescape.quote`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+
+import { arbitrary } from "../_.js";
+
+import { quote } from "shescape";
+
+testProp(
+  "return value",
+  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
+  (t, arg, options) => {
+    const result = quote(arg, options);
+    t.is(typeof result, "string");
+  },
+);

programming_language: TypeScript
filename: zsh.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quote` for the Z shell
+ * (Zsh).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quote } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binZsh}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binZsh)) {
+    const { expected, input, options } = scenario;
+    const result = quote(input, options);
+    t.is(result, expected);
+  }
+});

programming_language: TypeScript
filename: commonjs.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,53 @@
+/**
+ * @overview Contains integration tests for the CommonJS version of the testing
+ * implementation of shescape.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+import * as fc from "fast-check";
+
+import { arbitrary } from "../_.js";
+
+import { shescape as stubscape } from "shescape/testing";
+import { shescape as stubscapeCjs } from "../../../testing.cjs";
+
+testProp(
+  "escape (esm === cjs)",
+  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
+  (t, arg, options) => {
+    const resultEsm = stubscape.escape(arg, options);
+    const resultCjs = stubscapeCjs.escape(arg, options);
+    t.is(resultEsm, resultCjs);
+  },
+);
+
+testProp(
+  "escapeAll (esm === cjs)",
+  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
+  (t, args, options) => {
+    const resultEsm = stubscape.escapeAll(args, options);
+    const resultCjs = stubscapeCjs.escapeAll(args, options);
+    t.deepEqual(resultEsm, resultCjs);
+  },
+);
+
+testProp(
+  "quote (esm === cjs)",
+  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
+  (t, arg, options) => {
+    const resultEsm = stubscape.quote(arg, options);
+    const resultCjs = stubscapeCjs.quote(arg, options);
+    t.is(resultEsm, resultCjs);
+  },
+);
+
+testProp(
+  "quoteAll (esm === cjs)",
+  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
+  (t, args, options) => {
+    const resultEsm = stubscape.quoteAll(args, options);
+    const resultCjs = stubscapeCjs.quoteAll(args, options);
+    t.deepEqual(resultEsm, resultCjs);
+  },
+);

programming_language: JavaScript
filename: functional.test.js
change_type: ModificationType.RENAME
diff: @@ -7,11 +7,10 @@
 import { testProp } from "@fast-check/ava";
 import * as fc from "fast-check";
 
-import { arbitrary } from "./_.js";
+import { arbitrary } from "../_.js";
 
 import * as shescape from "shescape";
 import { shescape as stubscape } from "shescape/testing";
-import { shescape as stubscapeCjs } from "../../testing.cjs";
 
 testProp(
   "escape (stubscape ~ shescape)",
@@ -104,43 +103,3 @@ testProp(
     t.is(typeof result, typeof stubResult);
   },
 );
-
-testProp(
-  "escape (esm === cjs)",
-  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
-  (t, arg, options) => {
-    const resultEsm = stubscape.escape(arg, options);
-    const resultCjs = stubscapeCjs.escape(arg, options);
-    t.is(resultEsm, resultCjs);
-  },
-);
-
-testProp(
-  "escapeAll (esm === cjs)",
-  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
-  (t, args, options) => {
-    const resultEsm = stubscape.escapeAll(args, options);
-    const resultCjs = stubscapeCjs.escapeAll(args, options);
-    t.deepEqual(resultEsm, resultCjs);
-  },
-);
-
-testProp(
-  "quote (esm === cjs)",
-  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
-  (t, arg, options) => {
-    const resultEsm = stubscape.quote(arg, options);
-    const resultCjs = stubscapeCjs.quote(arg, options);
-    t.is(resultEsm, resultCjs);
-  },
-);
-
-testProp(
-  "quoteAll (esm === cjs)",
-  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
-  (t, args, options) => {
-    const resultEsm = stubscape.quoteAll(args, options);
-    const resultCjs = stubscapeCjs.quoteAll(args, options);
-    t.deepEqual(resultEsm, resultCjs);
-  },
-);

programming_language: TypeScript
filename: _.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,8 @@
+/**
+ * @overview Provides testing utilities.
+ * @license MIT
+ */
+
+import * as arbitrary from "../../_arbitraries.js";
+
+export { arbitrary };

programming_language: TypeScript
filename: resolve.test.js
change_type: ModificationType.MODIFY
diff: @@ -3,9 +3,13 @@
  * @license MIT
  */
 
+import { testProp } from "@fast-check/ava";
 import test from "ava";
+import * as fc from "fast-check";
 import sinon from "sinon";
 
+import { arbitrary } from "./_.js";
+
 import { resolveExecutable } from "../../../src/executables.js";
 
 test.before((t) => {
@@ -16,7 +20,11 @@ test.before((t) => {
   t.not(executable, linkedExecutable);
   t.not(resolvedExecutable, linkedExecutable);
 
-  t.context = { executable, linkedExecutable, resolvedExecutable };
+  const env = {
+    PATH: "/bin:/usr/bin",
+  };
+
+  t.context = { env, executable, linkedExecutable, resolvedExecutable };
 });
 
 test.beforeEach((t) => {
@@ -27,9 +35,52 @@ test.beforeEach((t) => {
   t.context.deps = { exists, readlink, which };
 });
 
+testProp(
+  "env.PATH is defined",
+  [arbitrary.env({ keys: ["PATH", "Path"] }), fc.string({ minLength: 1 })],
+  (t, env, envPath) => {
+    t.context.deps.which.resetHistory();
+
+    env.PATH = envPath;
+
+    const { executable } = t.context;
+    const args = { env, executable };
+
+    resolveExecutable(args, t.context.deps);
+    t.is(t.context.deps.which.callCount, 1);
+    t.true(
+      t.context.deps.which.calledWithExactly(sinon.match.any, {
+        path: env.PATH,
+      }),
+    );
+  },
+);
+
+testProp(
+  "env.PATH is not defined",
+  [arbitrary.env({ keys: ["PATH", "Path"] }), fc.string({ minLength: 1 })],
+  (t, env, envPath) => {
+    t.context.deps.which.resetHistory();
+
+    delete env.PATH;
+    env.Path = envPath;
+
+    const { executable } = t.context;
+    const args = { env, executable };
+
+    resolveExecutable(args, t.context.deps);
+    t.is(t.context.deps.which.callCount, 1);
+    t.true(
+      t.context.deps.which.calledWithExactly(sinon.match.any, {
+        path: env.Path,
+      }),
+    );
+  },
+);
+
 test("the executable cannot be resolved", (t) => {
-  const { executable } = t.context;
-  const args = { executable };
+  const { env, executable } = t.context;
+  const args = { env, executable };
 
   t.context.deps.which.throws();
 
@@ -37,15 +88,15 @@ test("the executable cannot be resolved", (t) => {
   t.is(result, executable);
 
   t.is(t.context.deps.which.callCount, 1);
-  t.true(t.context.deps.which.calledWithExactly(executable));
+  t.true(t.context.deps.which.calledWithExactly(executable, sinon.match.any));
 
   t.is(t.context.deps.exists.callCount, 0);
   t.is(t.context.deps.readlink.callCount, 0);
 });
 
 test("the executable doesn't exist", (t) => {
-  const { executable, resolvedExecutable } = t.context;
-  const args = { executable };
+  const { env, executable, resolvedExecutable } = t.context;
+  const args = { env, executable };
 
   t.context.deps.exists.returns(false);
   t.context.deps.which.returns(resolvedExecutable);
@@ -61,8 +112,8 @@ test("the executable doesn't exist", (t) => {
 });
 
 test("the executable exists and is not a (sym)link", (t) => {
-  const { executable, resolvedExecutable } = t.context;
-  const args = { executable };
+  const { env, executable, resolvedExecutable } = t.context;
+  const args = { env, executable };
 
   t.context.deps.exists.returns(true);
   t.context.deps.readlink.throws();
@@ -79,8 +130,8 @@ test("the executable exists and is not a (sym)link", (t) => {
 });
 
 test("the executable exists and is a (sym)link", (t) => {
-  const { executable, linkedExecutable, resolvedExecutable } = t.context;
-  const args = { executable };
+  const { env, executable, linkedExecutable, resolvedExecutable } = t.context;
+  const args = { env, executable };
 
   t.context.deps.exists.returns(true);
   t.context.deps.readlink.returns(linkedExecutable);

programming_language: TypeScript
filename: parse-options.test.js
change_type: ModificationType.MODIFY
diff: @@ -18,7 +18,7 @@ const arbitraryInput = () =>
     .tuple(arbitrary.shescapeOptions(), arbitrary.env())
     .map(([options, env]) => {
       options = options || {};
-      return { options, process: { env } };
+      return { env, options };
     });
 
 test.beforeEach((t) => {
@@ -84,7 +84,7 @@ testProp(
     t.context.deps.getShellName.resetHistory();
     t.context.deps.getShellName.returns(shellName);
 
-    const env = args.process.env;
+    const env = args.env;
     args.options.shell = providedShell;
 
     const result = parseOptions(args, t.context.deps);
@@ -93,7 +93,7 @@ testProp(
     t.is(t.context.deps.getShellName.callCount, 1);
     t.true(
       t.context.deps.getShellName.calledWithExactly(
-        { shell: defaultShell },
+        { env, shell: defaultShell },
         { resolveExecutable },
       ),
     );
@@ -108,6 +108,7 @@ testProp(
     t.context.deps.getShellName.resetHistory();
     t.context.deps.getShellName.returns(shellName);
 
+    const env = args.env;
     args.options.shell = providedShell;
 
     const result = parseOptions(args, t.context.deps);
@@ -115,7 +116,7 @@ testProp(
     t.is(t.context.deps.getShellName.callCount, 1);
     t.true(
       t.context.deps.getShellName.calledWithExactly(
-        { shell: providedShell },
+        { env, shell: providedShell },
         { resolveExecutable },
       ),
     );

programming_language: TypeScript
filename: index.test.js
change_type: ModificationType.MODIFY
diff: @@ -106,7 +106,7 @@ testProp(
     unix.getShellName({ env, shell }, { resolveExecutable });
     t.true(
       resolveExecutable.calledWithExactly(
-        { executable: shell },
+        { env, executable: shell },
         {
           exists: sinon.match.func,
           readlink: sinon.match.func,

programming_language: TypeScript
filename: index.test.js
change_type: ModificationType.MODIFY
diff: @@ -99,11 +99,15 @@ testProp(
   "get shell name for supported shell",
   [arbitrary.env(), arbitrary.windowsPath(), arbitrary.windowsShell()],
   (t, env, basePath, shell) => {
+    const executable = shell.toLowerCase().endsWith(".exe")
+      ? shell
+      : `${shell}.exe`;
+
     const resolveExecutable = sinon.stub();
-    resolveExecutable.returns(path.join(basePath, shell));
+    resolveExecutable.returns(path.join(basePath, executable));
 
     const result = win.getShellName({ env, shell }, { resolveExecutable });
-    t.is(result, shell);
+    t.is(result, executable);
   },
 );
 
@@ -133,7 +137,7 @@ testProp(
     win.getShellName({ env, shell }, { resolveExecutable });
     t.true(
       resolveExecutable.calledWithExactly(
-        { executable: shell },
+        { env, executable: shell },
         {
           exists: sinon.match.func,
           readlink: sinon.match.func,

programming_language: TypeScript