CVE-2025-30222
Description
Shescape is a simple shell escape library for JavaScript. Versions 1.7.2 through 2.1.1 are vulnerable to potential environment variable exposure on Windows with CMD. This impact users of Shescape on Windows that explicitly configure shell: 'cmd.exe' or shell: true using any of quote/quoteAll/escape/escapeAll. An attacker may be able to get read-only access to environment variables. This bug has been patched in v2.1.2. For those who are already using v2 of Shescape, no further changes are required. Those who are are using v1 of Shescape should follow the migration guide to upgrade to v2. There is no plan to release a patch compatible with v1 of Shescape. As a workaround, users can remove all instances of % from user input before using Shescape.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
shescapenpm | >= 1.7.2, < 2.1.2 | 2.1.2 |
Affected products
1- Range: v1.7.2, v1.7.3, v1.7.4, …
Patches
2155b13b414170a81f1eb077bCorrect escaping of `%` escaping for CMD (#1916)
4 files changed · +54 −70
CHANGELOG.md+2 −1 modified@@ -9,7 +9,7 @@ Versioning]. ## [Unreleased] -- _No changes yet_ +- Correct escaping of `%` escaping for CMD. ([#1916]) ## [2.1.1] - 2024-05-01 @@ -340,6 +340,7 @@ Versioning]. [#1308]: https://github.com/ericcornelissen/shescape/pull/1308 [#1530]: https://github.com/ericcornelissen/shescape/pull/1530 [#1536]: https://github.com/ericcornelissen/shescape/pull/1536 +[#1916]: https://github.com/ericcornelissen/shescape/pull/1916 [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
src/internal/win/cmd.js+1 −19 modified@@ -10,29 +10,11 @@ * @returns {string} The escaped argument. */ function escapeArg(arg) { - let shouldEscapeSpecialChar = true; return arg .replace(/[\0\u0008\r\u001B\u009B]/gu, "") .replace(/\n/gu, " ") .replace(/(?<!\\)(\\*)"/gu, '$1$1\\"') - .split("") - .map( - // Due to the way CMD determines if it is inside a quoted section, and the - // way we escape double quotes, whether or not special character need to - // be escaped depends on the number of double quotes that proceed it. So, - // we flip a flag for every double quote we encounter and escape special - // characters conditionally on that flag. - (char) => { - if (char === '"') { - shouldEscapeSpecialChar = !shouldEscapeSpecialChar; - } else if (shouldEscapeSpecialChar && /[%&<>^|]/u.test(char)) { - return `^${char}`; - } - - return char; - }, - ) - .join(""); + .replace(/(["%&<>^|])/gu, "^$1"); } /**
test/fixtures/win.js+50 −50 modified@@ -1752,19 +1752,19 @@ export const escape = { "double quotes ('\"')": [ { input: 'a"b', - expected: 'a\\"b', + expected: 'a\\^"b', }, { input: 'a"b"c', - expected: 'a\\"b\\"c', + expected: 'a\\^"b\\^"c', }, { input: 'a"', - expected: 'a\\"', + expected: 'a\\^"', }, { input: '"a', - expected: '\\"a', + expected: '\\^"a', }, ], "backticks ('`')": [ @@ -1842,15 +1842,15 @@ export const escape = { "carets ('^') + double quotes ('\"')": [ { input: 'a"b^c', - expected: 'a\\"b^c', + expected: 'a\\^"b^^c', }, { input: 'a"b"c^d', - expected: 'a\\"b\\"c^^d', + expected: 'a\\^"b\\^"c^^d', }, { input: 'a^b"c', - expected: 'a^^b\\"c', + expected: 'a^^b\\^"c', }, ], "dollar signs ('$')": [ @@ -1892,15 +1892,15 @@ export const escape = { "percentage signs ('%') + double quotes ('\"')": [ { input: 'a"b%c', - expected: 'a\\"b%c', + expected: 'a\\^"b^%c', }, { input: 'a"b"c%d', - expected: 'a\\"b\\"c^%d', + expected: 'a\\^"b\\^"c^%d', }, { input: 'a%b"c', - expected: 'a^%b\\"c', + expected: 'a^%b\\^"c', }, ], "ampersands ('&')": [ @@ -1924,15 +1924,15 @@ export const escape = { "ampersands ('&') + double quotes ('\"')": [ { input: 'a"b&c', - expected: 'a\\"b&c', + expected: 'a\\^"b^&c', }, { input: 'a"b"c&d', - expected: 'a\\"b\\"c^&d', + expected: 'a\\^"b\\^"c^&d', }, { input: 'a&b"c', - expected: 'a^&b\\"c', + expected: 'a^&b\\^"c', }, ], "hyphens ('-')": [ @@ -2028,15 +2028,15 @@ export const escape = { "pipes ('|') + double quotes ('\"')": [ { input: 'a"b|c', - expected: 'a\\"b|c', + expected: 'a\\^"b^|c', }, { input: 'a"b"c|d', - expected: 'a\\"b\\"c^|d', + expected: 'a\\^"b\\^"c^|d', }, { input: 'a|b"c', - expected: 'a^|b\\"c', + expected: 'a^|b\\^"c', }, ], "comma (',')": [ @@ -2212,27 +2212,27 @@ export const escape = { "angle brackets ('<', '>') + double quotes ('\"')": [ { input: 'a"b>c', - expected: 'a\\"b>c', + expected: 'a\\^"b^>c', }, { input: 'a"b<c', - expected: 'a\\"b<c', + expected: 'a\\^"b^<c', }, { input: 'a"b"c>d', - expected: 'a\\"b\\"c^>d', + expected: 'a\\^"b\\^"c^>d', }, { input: 'a"b"c<d', - expected: 'a\\"b\\"c^<d', + expected: 'a\\^"b\\^"c^<d', }, { input: 'a>b"c', - expected: 'a^>b\\"c', + expected: 'a^>b\\^"c', }, { input: 'a<b"c', - expected: 'a^<b\\"c', + expected: 'a^<b\\^"c', }, ], "left double quotation mark ('“')": [ @@ -4490,47 +4490,47 @@ export const quote = { "double quotes ('\"')": [ { input: 'a"b', - expected: 'a\\"b', + expected: 'a\\^"b', }, { input: 'a"b"c', - expected: 'a\\"b\\"c', + expected: 'a\\^"b\\^"c', }, { input: 'a"', - expected: 'a\\"', + expected: 'a\\^"', }, { input: '"a', - expected: '\\"a', + expected: '\\^"a', }, { input: 'a""b', - expected: 'a\\"\\"b', + expected: 'a\\^"\\^"b', }, ], "double quotes ('\"') + whitespace": [ { input: 'a "b', - expected: 'a" "\\"b', + expected: 'a" "\\^"b', }, { input: 'a" b', - expected: 'a\\"" "b', + expected: 'a\\^"" "b', }, { input: 'a " b', - expected: 'a" "\\"" "b', + expected: 'a" "\\^"" "b', }, ], "double quotes ('\"') + backslashes ('\\')": [ { input: 'a\\"b', - expected: 'a\\\\\\"b', + expected: 'a\\\\\\^"b', }, { input: 'a\\\\"b', - expected: 'a\\\\\\\\\\"b', + expected: 'a\\\\\\\\\\^"b', }, ], "backticks ('`')": [ @@ -4572,15 +4572,15 @@ export const quote = { "carets ('^') + double quotes ('\"')": [ { input: 'a"b^c', - expected: 'a\\"b^c', + expected: 'a\\^"b^^c', }, { input: 'a"b"c^d', - expected: 'a\\"b\\"c^^d', + expected: 'a\\^"b\\^"c^^d', }, { input: 'a^b"c', - expected: 'a^^b\\"c', + expected: 'a^^b\\^"c', }, ], "dollar signs ('$')": [ @@ -4622,15 +4622,15 @@ export const quote = { "percentage signs ('%') + double quotes ('\"')": [ { input: 'a"b%c', - expected: 'a\\"b%c', + expected: 'a\\^"b^%c', }, { input: 'a"b"c%d', - expected: 'a\\"b\\"c^%d', + expected: 'a\\^"b\\^"c^%d', }, { input: 'a%b"c', - expected: 'a^%b\\"c', + expected: 'a^%b\\^"c', }, ], "ampersands ('&')": [ @@ -4654,15 +4654,15 @@ export const quote = { "ampersands ('&') + double quotes ('\"')": [ { input: 'a"b&c', - expected: 'a\\"b&c', + expected: 'a\\^"b^&c', }, { input: 'a"b"c&d', - expected: 'a\\"b\\"c^&d', + expected: 'a\\^"b\\^"c^&d', }, { input: 'a&b"c', - expected: 'a^&b\\"c', + expected: 'a^&b\\^"c', }, ], "hyphens ('-')": [ @@ -4722,15 +4722,15 @@ export const quote = { "pipes ('|') + double quotes ('\"')": [ { input: 'a"b|c', - expected: 'a\\"b|c', + expected: 'a\\^"b^|c', }, { input: 'a"b"c|d', - expected: 'a\\"b\\"c^|d', + expected: 'a\\^"b\\^"c^|d', }, { input: 'a|b"c', - expected: 'a^|b\\"c', + expected: 'a^|b\\^"c', }, ], "angle brackets ('<', '>')": [ @@ -4774,27 +4774,27 @@ export const quote = { "angle brackets ('<', '>') + double quotes ('\"')": [ { input: 'a"b>c', - expected: 'a\\"b>c', + expected: 'a\\^"b^>c', }, { input: 'a"b<c', - expected: 'a\\"b<c', + expected: 'a\\^"b^<c', }, { input: 'a"b"c>d', - expected: 'a\\"b\\"c^>d', + expected: 'a\\^"b\\^"c^>d', }, { input: 'a"b"c<d', - expected: 'a\\"b\\"c^<d', + expected: 'a\\^"b\\^"c^<d', }, { input: 'a>b"c', - expected: 'a^>b\\"c', + expected: 'a^>b\\^"c', }, { input: 'a<b"c', - expected: 'a^<b\\"c', + expected: 'a^<b\\^"c', }, ], "left double quotation mark ('“')": [
test/fuzz/corpus/fc01cea8ecef9dedd1b4d22dde5cb82e90000522+1 −0 added@@ -0,0 +1 @@ +"%PATH%
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- github.com/advisories/GHSA-66pp-5p9w-q87jghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-30222ghsaADVISORY
- github.com/ericcornelissen/shescape/commit/0a81f1eb077bab8caae283a2490cd7be9af179c6nvdWEB
- github.com/ericcornelissen/shescape/pull/1916nvdWEB
- github.com/ericcornelissen/shescape/releases/tag/v2.1.2nvdWEB
- github.com/ericcornelissen/shescape/security/advisories/GHSA-66pp-5p9w-q87jnvdWEB
News mentions
0No linked articles in our index yet.