Jul 25, 2026·7 min read·22 visits
Shescape failed to escape critical shell control characters when placed immediately after punctuation, leading to path disclosure in Unix shells and syntax breakout in Windows cmd.exe.
An incomplete escaping vulnerability in the npm package 'shescape' allows unauthenticated users to trigger dynamic shell expansions, absolute path disclosure, and command block break-outs on Unix and Windows systems.
The NPM library shescape is a utility designed to escape command-line arguments to prevent command injection vulnerabilities in Node.js applications. When developers execute shell commands using dynamic arguments, they rely on escaping mechanisms to neutralize special characters that shells interpret as control operators. This vulnerability represents a parser differential where the library's escaping logic fails to align with the parsing behavior of downstream shell interpreters such as Zsh, Dash, and Windows Command Prompt.
In Unix-like systems, character sequences like ~ (tilde expansion), ^ (extended globbing), and # (comments) possess active properties depending on active shell configuration options. Similarly, Windows cmd.exe utilizes parentheses ( and ) for command grouping in control blocks. Prior to the fix, Shescape executed conditional, pattern-based escaping that only neutralized these characters when they appeared at the start of an argument or were immediately preceded by whitespace.
Because of this design flaw, an attacker can input arguments where active shell characters are preceded by other punctuation characters such as = or :. Shescape processes these patterns as safe literals and skips escaping them. However, when the downstream system shell parses the argument, it executes the dynamic expansion or structural evaluation, bypassing the security boundaries established by the library.
The primary root cause of this vulnerability lies in the context-restricted regular expressions used in Shescape's escape functions. For Unix platforms running Zsh and Dash, the package attempted to optimize or restrict escaping of tildes and hashtags to minimize unnecessary backslashes. It implemented regular expressions that required a preceding space or the start of the string to trigger an escape action.
For example, the old Zsh regexes were defined as:
const comments = new RegExp(/(^|\s)#/g);
const expansions = new RegExp(/(^|\s)([=~])/g);These regular expressions fail to match if a tilde or hashtag is preceded by punctuation instead of whitespace. In standard POSIX shells and Dash, tilde expansion is performed not only at the beginning of a word but also immediately following a colon : or an equals sign = (common in environment variable definitions or path lists). Zsh, particularly with the EXTENDED_GLOB option enabled, treats ^ and ~ as active pattern operators regardless of their placement relative to whitespace.
Additionally, the Windows Command Prompt escaping module completely omitted parentheses from its set of special characters. The old regular expression was defined as:
const specials = new RegExp(/([%&<>^|])/g);Because parentheses were not classified as special characters, they passed through the escape module unaltered. This allowed unescaped execution context manipulation within complex shell blocks. Finally, the internal composition logic in src/internal/compose.js parsed multi-segment flags using an array-shifting loop that could fail to align subsequent escape configurations if a segment returned an empty string, creating an auxiliary bypass vector.
The vulnerability was resolved across two release branches: the v3 line (patched in 3.0.1) and the v2 line (backported in 2.1.14). The core fix shifts Shescape from conditional, context-restricted regular expressions to unconditional escaping of unsafe shell control characters.
In src/internal/unix/zsh.js, the conditional tilde and hashtag matchers were removed. The #, ^, and ~ characters were added directly to the unconditional specials character class:
- const comments = new RegExp(/(^|\s)#/g);
- const expansions = new RegExp(/(^|\s)([=~])/g);
- const specials = new RegExp(/(["$&'()*;<>?[\]`{|}])/g);
+ const expansions = new RegExp(/(^|[\s:=])=/g);
+ const specials = new RegExp(/(["#$&'()*;<>?[\]^`{|}~])/g);For src/internal/unix/dash.js, the tilde matching regex was widened to capture assignments and path prefixes starting with : or =. This aligns the escaping boundary with standard POSIX assignment behavior:
- const home = new RegExp(/(^|\s)~/g);
+ const home = new RegExp(/(^|[\s:=])~/g);For Windows environments in src/internal/win/cmd.js, parentheses were integrated into the specials regex to prevent syntax breakouts inside parenthesized command groups:
- const specials = new RegExp(/([%&<>^|])/g);
+ const specials = new RegExp(/([%&()<>^|])/g);In src/internal/compose.js, the array-shifting composition loop was refactored. The fragile array-destructuring loop was replaced with an index-based boundary loop that evaluates the returned values of escape functions reliably:
- let [preFlag, , ...rest] = flagFn(arg);
- while (rest.length > 0 && escapeFn(preFlag) === "") {
- arg = rest.join("");
- [preFlag, , ...rest] = rest;
- }
+ const fragments = flagFn(arg);
+
+ let idx = 0;
+ for (; idx < fragments.length - 2; idx += 2) {
+ const escapedFragment = escapeFn(fragments[idx]);
+ if (escapedFragment !== "") {
+ break;
+ }
+ }
+
+ arg = fragments.slice(idx).join("");Exploitation of this vulnerability requires the target application to pass unsanitized user input through Shescape into a shell execution context. The exact security impact varies based on the target OS, shell, and shell-specific options.
Consider an application that defines an execution wrapper such as:
import { escape } from 'shescape';
import { exec } from 'child_process';
function runCommand(userInput) {
const escapedInput = escape(userInput);
exec(`echo ${escapedInput}`, { shell: '/bin/zsh' }, (err, stdout) => {
console.log(stdout);
});
}An attacker can supply the input test=~ to trigger the vulnerability. When Shescape processes this input, the conditional regex /(^|\s)([=~])/ fails to match because the tilde is preceded by =. The input is returned unescaped as test=~. When the command is passed to /bin/zsh, the shell performs tilde expansion on the assignment structure, converting the tilde into the absolute path of the executing user (e.g., /home/node-app). The command executed becomes echo test=/home/node-app, leaking the internal directory structure in the output.
Under Windows cmd.exe, a similar bypass exists when parameters are executed inside standard control blocks (such as FOR loops or IF statements). By injecting parentheses such as ) & calc.exe & (, an attacker can close the application's native command group, execute arbitrary commands, and open a new group to satisfy syntax requirements, leading to remote command execution.
The primary impact of this vulnerability is information disclosure in the form of absolute path disclosure. By forcing the shell to expand tildes, attackers can map out the backend server's file system structure, identify valid username directories, and confirm the presence of specific files or directories.
If the application operates within Zsh and has EXTENDED_GLOB enabled, an attacker can use the negation operator ^ or other globbing operators to interact with the file system. For example, passing ^exclusion_pattern can trigger directory listings of all files not matching the pattern, resulting in file enumeration.
On Windows systems running cmd.exe, the impact can escalate to remote code execution. Because parenthesized command blocks can be broken out of, an attacker who controls parameters parsed inside IF or FOR statements can inject arbitrary shell commands. This changes the impact from low-severity information disclosure to high-severity integrity and confidentiality compromise.
Remediation of this vulnerability requires upgrading the shescape dependency to the appropriate patched release. The development team has issued patches for both major release lines.
For applications utilizing the 2.x branch, upgrade to version 2.1.14 or later. For applications utilizing the 3.x branch, upgrade to version 3.0.1 or later. These versions contain the updated, context-agnostic regular expressions and the corrected flag composition logic.
If upgrading is not immediately feasible, you can apply temporary mitigation strategies. Validate and sanitize user inputs to reject characters like ~, #, ^, (, and ) before passing them to the escaping library. Alternatively, restructure system execution routines to avoid launching commands via a shell interpreter. Using APIs like child_process.execFile or child_process.spawn with direct argument arrays avoids the shell parser entirely, neutralizing this class of vulnerability.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
shescape ericcornelissen | < 2.1.14 | 2.1.14 |
shescape ericcornelissen | == 3.0.0 | 3.0.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-200 / CWE-20 |
| Attack Vector | Local / Network |
| CVSS v3.1 Score | 6.5 |
| Exploit Status | poc |
| CISA KEV Status | Not Listed |
| Impact | Absolute Path Disclosure / Command Execution Bypass |
The product exposes sensitive information to an actor who is not authorized to have access to that information.
Nuxt versions prior to 4.6.2 suffer from a route rule bypass vulnerability caused by an architectural mismatch in case-sensitivity handling between the Nitro server runtime and the Vue page router. An unauthenticated remote attacker can bypass server-enforced route directives—such as redirects, security headers, and SSR controls—by altering character casing in HTTP request paths.
A heap-based buffer overflow vulnerability in Pillow PyPI binary wheels (versions 11.1.0 to 12.x) allows remote code execution or application crash via crafted JPEG 2000 files processed by the bundled libopenjp2 library.
CVE-2026-109618 (dubbed PHP2Shell) is a critical vulnerability in the PHP engine core resulting from improper out-of-memory exception handling via longjmp(). When request allocation fails mid-processing, non-local control flow bypasses cleanup procedures, leaving memory structures uninitialized or in a use-after-free state during request shutdown.
CVE-2026-109619 (PHP2Shell) is a critical Use-After-Free vulnerability in the core PHP engine caused by improper global state cleanup during out-of-memory exception handling across persistent worker requests.
In Python virtualenv prior to version 21.14.9, activation script generators and configuration writers use Path.exists() before writing files. Because Path.exists() resolves symbolic links, it returns False for dangling symlinks. Consequently, virtualenv skips unlinking pre-planted symbolic links or hard links, writing data directly to target locations outside the intended virtual environment directory.
A remote code execution vulnerability exists in Microsoft .NET due to untrusted data deserialization within the PresentationCore component. The flaw stems from implicit auto-conversion flags during clipboard data retrieval operations.