VYPR
High severityNVD Advisory· Published Aug 23, 2023· Updated Sep 30, 2024

Shescape on Windows escaping may be bypassed in threaded context

CVE-2023-40185

Description

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.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
shescapenpm
< 1.7.41.7.4

Affected products

1

Patches

1
0b976dab645a

Provide explicit `$PATH` value to which (#1142)

https://github.com/ericcornelissen/shescapeEric CornelissenAug 21, 2023via ghsa
68 files changed · +1174 302
  • CHANGELOG.md+2 0 modified
    @@ -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
    
  • index.js+2 2 modified
    @@ -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);
    
  • src/executables.js+6 2 modified
    @@ -12,16 +12,20 @@
      * - 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.
      * @param {Function} deps.readlink A function to resolve (sym)links.
      * @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
    
  • src/options.js+3 4 modified
    @@ -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 };
     }
    
  • src/unix.js+3 2 modified
    @@ -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 },
       );
     
    
  • src/win.js+3 2 modified
    @@ -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 },
       );
     
    
  • test/_constants.cjs+4 2 modified
    @@ -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,
     ];
    
  • test/e2e/child_process.test.js+0 40 removed
    @@ -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 });
    -  }
    -}
    
  • test/e2e/_common.js+53 0 added
    @@ -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];
    +}
    
  • test/e2e/exec-file.test.js+15 0 added
    @@ -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 });
    +  }
    +}
    
  • test/e2e/exec.test.js+15 0 added
    @@ -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 });
    +  }
    +}
    
  • test/e2e/fork.test.js+12 0 added
    @@ -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 });
    +}
    
  • test/e2e/_.js+2 2 modified
    @@ -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 };
    
  • test/e2e/spawn.test.js+15 0 added
    @@ -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 });
    +  }
    +}
    
  • test/integration/escape-all/bash.test.js+21 0 added
    @@ -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]);
    +  }
    +});
    
  • test/integration/escape-all/cmd-exe.test.js+21 0 added
    @@ -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]);
    +  }
    +});
    
  • test/integration/escape-all/cmd.test.js+21 0 added
    @@ -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]);
    +  }
    +});
    
  • test/integration/escape-all/commonjs.test.js+23 0 added
    @@ -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);
    +  },
    +);
    
  • test/integration/escape-all/csh.test.js+21 0 added
    @@ -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]);
    +  }
    +});
    
  • test/integration/escape-all/dash.test.js+21 0 added
    @@ -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]);
    +  }
    +});
    
  • test/integration/escape-all/invalid.test.js+22 0 added
    @@ -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);
    +});
    
  • test/integration/escape-all/powershell-exe.test.js+21 0 added
    @@ -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]);
    +  }
    +});
    
  • test/integration/escape-all/powershell.test.js+23 0 added
    @@ -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]);
    +  }
    +});
    
  • test/integration/escape-all/valid.test.js+2 34 renamed
    @@ -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);
    -  },
    -);
    
  • test/integration/escape-all/zsh.test.js+21 0 added
    @@ -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]);
    +  }
    +});
    
  • test/integration/escape/bash.test.js+21 0 added
    @@ -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);
    +  }
    +});
    
  • test/integration/escape/cmd-exe.test.js+21 0 added
    @@ -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);
    +  }
    +});
    
  • test/integration/escape/cmd.test.js+21 0 added
    @@ -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);
    +  }
    +});
    
  • test/integration/escape/commonjs.test.js+22 0 added
    @@ -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);
    +  },
    +);
    
  • test/integration/escape/csh.test.js+21 0 added
    @@ -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);
    +  }
    +});
    
  • test/integration/escape/dash.test.js+21 0 added
    @@ -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);
    +  }
    +});
    
  • test/integration/escape/invalid.test.js+21 0 added
    @@ -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);
    +});
    
  • test/integration/escape/powershell-exe.test.js+21 0 added
    @@ -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);
    +  }
    +});
    
  • test/integration/escape/powershell.test.js+23 0 added
    @@ -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);
    +  }
    +});
    
  • test/integration/escape.test.js+0 50 removed
    @@ -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);
    -  },
    -);
    
  • test/integration/escape/valid.test.js+19 0 added
    @@ -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");
    +  },
    +);
    
  • test/integration/escape/zsh.test.js+21 0 added
    @@ -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);
    +  }
    +});
    
  • test/integration/_generators.js+9 18 modified
    @@ -5,28 +5,16 @@
     
     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.
      *
      * @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(),
       };
     }
     
    
  • test/integration/quote-all/bash.test.js+21 0 added
    @@ -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]);
    +  }
    +});
    
  • test/integration/quote-all/cmd-exe.test.js+21 0 added
    @@ -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]);
    +  }
    +});
    
  • test/integration/quote-all/cmd.test.js+21 0 added
    @@ -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]);
    +  }
    +});
    
  • test/integration/quote-all/commonjs.test.js+23 0 added
    @@ -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);
    +  },
    +);
    
  • test/integration/quote-all/csh.test.js+21 0 added
    @@ -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]);
    +  }
    +});
    
  • test/integration/quote-all/dash.test.js+21 0 added
    @@ -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]);
    +  }
    +});
    
  • test/integration/quote-all/invalid.test.js+22 0 added
    @@ -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);
    +});
    
  • test/integration/quote-all/powershell-exe.test.js+21 0 added
    @@ -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]);
    +  }
    +});
    
  • test/integration/quote-all/powershell.test.js+21 0 added
    @@ -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]);
    +  }
    +});
    
  • test/integration/quote-all/valid.test.js+2 34 renamed
    @@ -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);
    -  },
    -);
    
  • test/integration/quote-all/zsh.test.js+21 0 added
    @@ -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]);
    +  }
    +});
    
  • test/integration/quote/bash.test.js+21 0 added
    @@ -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);
    +  }
    +});
    
  • test/integration/quote/cmd-exe.test.js+21 0 added
    @@ -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);
    +  }
    +});
    
  • test/integration/quote/cmd.test.js+21 0 added
    @@ -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);
    +  }
    +});
    
  • test/integration/quote/commonjs.test.js+22 0 added
    @@ -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);
    +  },
    +);
    
  • test/integration/quote/csh.test.js+21 0 added
    @@ -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);
    +  }
    +});
    
  • test/integration/quote/dash.test.js+21 0 added
    @@ -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);
    +  }
    +});
    
  • test/integration/quote/invalid.test.js+21 0 added
    @@ -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);
    +});
    
  • test/integration/quote/powershell-exe.test.js+21 0 added
    @@ -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);
    +  }
    +});
    
  • test/integration/quote/powershell.test.js+21 0 added
    @@ -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);
    +  }
    +});
    
  • test/integration/quote.test.js+0 50 removed
    @@ -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);
    -  },
    -);
    
  • test/integration/quote/valid.test.js+19 0 added
    @@ -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");
    +  },
    +);
    
  • test/integration/quote/zsh.test.js+21 0 added
    @@ -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);
    +  }
    +});
    
  • test/integration/testing/commonjs.test.js+53 0 added
    @@ -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);
    +  },
    +);
    
  • test/integration/testing/functional.test.js+1 42 renamed
    @@ -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);
    -  },
    -);
    
  • test/unit/executables/_.js+8 0 added
    @@ -0,0 +1,8 @@
    +/**
    + * @overview Provides testing utilities.
    + * @license MIT
    + */
    +
    +import * as arbitrary from "../../_arbitraries.js";
    +
    +export { arbitrary };
    
  • test/unit/executables/resolve.test.js+61 10 modified
    @@ -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,25 +35,68 @@ 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();
     
       const result = resolveExecutable(args, t.context.deps);
       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);
    
  • test/unit/options/parse-options.test.js+5 4 modified
    @@ -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,14 +108,15 @@ 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);
         t.is(t.context.deps.getDefaultShell.callCount, 0);
         t.is(t.context.deps.getShellName.callCount, 1);
         t.true(
           t.context.deps.getShellName.calledWithExactly(
    -        { shell: providedShell },
    +        { env, shell: providedShell },
             { resolveExecutable },
           ),
         );
    
  • test/unit/unix/index.test.js+1 1 modified
    @@ -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,
    
  • test/unit/win/index.test.js+7 3 modified
    @@ -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,
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.