Jul 25, 2026·7 min read·3 visits
Shescape is vulnerable to an O(N^2) algorithmic complexity Denial of Service due to nested array copying and string joining inside its default flag-protection processing loop.
A high-severity algorithmic-complexity vulnerability in the Node.js library Shescape leads to a Quadratic-Time Denial of Service (DoS) when flag protection is enabled. By supplying inputs containing repetitive control characters and hyphens, remote attackers can trigger an inefficient nested loop in Shescape's argument-composition logic, blocking the Node.js single-threaded event loop and causing total application Denial of Service.
Shescape is an open-source command-argument escaping library designed to defend Node.js applications against command injection attacks. It is widely deployed to safely parse and escape untrusted user inputs before passing them to operating system shell processors. The default operating configuration of Shescape includes an active security control known as flag protection. This control is designed to identify and process arguments that begin with a hyphen, preventing attackers from injecting arbitrary flags into system utilities.
The default implementation of this flag-protection subsystem contains a critical algorithmic vulnerability. When processing inputs that contain repeated sequences of null characters and hyphens, the underlying argument-composition loop degrades into quadratic time complexity. This vulnerability is classified under CWE-407 (Inefficient Algorithmic Complexity) and CWE-400 (Uncontrolled Resource Consumption).
For applications executing in the single-threaded Node.js runtime, this algorithmic degradation represents an immediate Denial of Service vector. Because the CPU-intensive evaluation occurs entirely within the main execution thread, the event loop becomes completely blocked. Remote unauthenticated attackers can exploit this behavior by submitting small, specially crafted request parameters, exhausting server CPU resources and halting all concurrent operations.
The root cause of this vulnerability lies within the compose function located in the source file src/internal/compose.js. This function wraps the core escaping routines in a protective layer that iteratively strips and validates flags. The pre-patched code parses arguments into a structural array using a flag-extraction utility. It then enters a while loop that continues execution as long as there are remaining array elements and the parsed leading element escapes to an empty string.
During each iteration of this loop, the application performs two operations that scale linearly with the size of the remaining input. First, it reconstructs the working argument string by executing rest.join(""). Second, it performs an array destructuring assignment using the rest operator: [preFlag, , ...rest] = rest. This destructuring pattern forces the JavaScript engine to allocate a new array structure and execute a shallow copy of all remaining elements.
Because these linear O(N) operations are nested directly inside a loop that iterates up to N times, the overall execution performance degrades quadratically (O(N^2)). If the input argument consists of N repetitive sequences of characters that resolve to empty escaped strings, the engine must perform N*(N-1)/2 copy and join steps. An input containing 32,000 such segments forces the loop to run 32,000 times, performing intensive allocation and string construction on each pass, which completely blocks the CPU.
The vulnerable implementation of the flag-protection loop in src/internal/compose.js is structured as follows:
export function compose({ escapeFn, flagFn, quoteFn }) {
return (arg) => {
// flagFn splits the input argument into fragments
let [preFlag, , ...rest] = flagFn(arg);
// The while loop iterates if the escaped prefix is empty
while (rest.length > 0 && escapeFn(preFlag) === "") {
// LINEAR OPERATION: Reconstructs string by joining remaining elements
arg = rest.join("");
// LINEAR OPERATION: Destructuring creates a shallow copy of the array slice
[preFlag, , ...rest] = rest;
}
return escape(arg);
};
}The security patch introduced in commit 43d70b59d09bbe5c3fd02ef08b3a123e977ed9de resolves this computational bottleneck. Rather than modifying and copying the array during each iteration, the patched version implements an index-based linear scanning loop:
export function compose({ escapeFn, flagFn, quoteFn }) {
return (arg) => {
const fragments = flagFn(arg);
let idx = 0;
// CONSTANT TIME LOOP: Increments index counter to traverse elements
for (; idx < fragments.length - 2; idx += 2) {
const escapedFragment = escapeFn(fragments[idx]);
if (escapedFragment !== "") {
break;
}
}
// SINGLE LINEAR OPERATION: Performed exactly once after loop termination
arg = fragments.slice(idx).join("");
return escape(arg);
};
}This refactored logic replaces the nested O(N) copying operations with simple index incrementation. The loop body executes in O(1) constant time, and the expensive slice and join operations are deferred until the traversal has completed. This ensures that the overall time complexity remains strictly linear (O(N)) relative to the size of the array fragments.
Exploitation of this vulnerability requires the attacker to submit a payload that repeatedly satisfies the loop's continuation condition. The condition requires that the parsed fragment preFlag evaluates to an empty string under the configured escapeFn. The null byte character (\u0000) meets this criterion because Shescape's escape functions sanitize or strip null bytes, resulting in an empty string representation "".
To maintain the structure expected by the flag parser, each null byte must be accompanied by a hyphen delimiter. The resulting payload pattern is a continuous sequence of \u0000-. When the flag parser processes this input, it splits the payload into thousands of small fragments. On each iteration of the loop, the engine encounters a null byte, evaluates its escaped value to "", and proceeds to execute the nested O(N) string reconstruction and array copying operations.
A proof-of-concept script executing this attack demonstrates that a payload containing 32,000 repetitions of \u0000- is sufficient to freeze the Node.js event loop for approximately 20 seconds. This block duration scales exponentially as the size of the payload increases. Because the execution is synchronous, no other concurrent network requests or asynchronous events can be processed by the server during this window.
The security impact of this vulnerability is classified as high, receiving a CVSS v4.0 score of 8.7. The primary impact is on application availability. Because Node.js is designed with a single-threaded event loop, any synchronous operation that blocks execution halts the processing of all incoming HTTP requests. An attacker can exploit this to cause service unavailability with minimal network bandwidth.
Unlike memory corruption vulnerabilities that might lead to remote code execution or data leakage, this flaw does not compromise data confidentiality or integrity. The attack requirements are extremely low: no special privileges or user interactions are required to trigger the vulnerable code path. Any exposed input vector—such as form fields, query parameters, or file upload names—that routes unsanitized input to Shescape is a potential vector.
Furthermore, because Shescape is typically positioned on sensitive input-validation boundaries immediately before command execution, this vulnerability is often present in core security pipelines. If a monitoring system or load balancer attempts to check the health of an affected node while it is processing a malicious payload, the health check will fail, potentially causing container orchestrators to aggressively restart the instance and amplify the Denial of Service state across the infrastructure.
Remediation of GHSA-gm3r-q2wp-hw87 requires upgrading the Shescape library to a patched release. For applications utilizing the 3.x release line, the package must be updated to version 3.0.1 or higher. For legacy environments running the 2.x line, the package must be updated to the backported security release 2.1.14.
If an immediate package upgrade is not feasible, several defensive workarounds can be implemented at the application layer. The most effective temporary control is to enforce strict input validation on the length of all string arguments passed to Shescape. Limiting the maximum character length to a reasonable threshold, such as 256 characters, ensures that the quadratic complexity loop cannot scale to a point where it significantly degrades CPU performance.
Alternatively, applications can explicitly disable the flag-protection feature by initializing the library with the flagProtection option set to false. This bypasses the vulnerable code pathway entirely. However, this workaround must be applied with caution; disabling flag protection removes the defensive controls that prevent command-line flag injection, potentially exposing the application to argument-injection vulnerabilities if the processed inputs are subsequently used in sensitive shell execution contexts.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
shescape Eric Cornelissen | >= 2.1.11, < 2.1.14 | 2.1.14 |
shescape Eric Cornelissen | >= 3.0.0, < 3.0.1 | 3.0.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-407 / CWE-400 |
| Attack Vector | Network |
| CVSS v4.0 Score | 8.7 (High) |
| Exploit Status | PoC Available |
| Remediation | Upgrade to v2.1.14 or v3.0.1 |
| Impact | Application Denial of Service (CPU Exhaustion) |
The product uses an algorithm with an inefficient worst-case time complexity, such as quadratic or exponential, allowing attackers to cause resource exhaustion.
An escaping bypass in the npm package shescape on Unix platforms utilizing the Dash shell can result in unauthorized home directory path disclosure. The vulnerability occurs when user input containing specific sequences is passed inside shell variable assignment contexts. Additionally, the patch addresses secondary security gaps related to Zsh extended globbing and Windows CMD command block breakouts.
A multi-vector vulnerability advisory in the OmniFaces JavaServer Faces (JSF) utility library covers flaws leading to unauthenticated arbitrary file access, memory exhaustion (DoS), cross-site scripting (XSS), and WebSocket push channel hijacking.
Oh My Posh prior to version 29.35.1 is vulnerable to terminal escape sequence injection. Dynamic strings from directory names or Git repository metadata are written to the prompt output without neutralization, enabling unauthenticated remote code execution, clipboard hijacking, or terminal DoS when users navigate to malicious folders.
A critical-severity template injection vulnerability in Oh My Posh allows for unauthenticated arbitrary command execution. This issue occurs when a user navigates their shell into a directory whose name contains malicious Go template syntax, which is subsequently parsed and executed by the prompt engine.
A prototype pollution vulnerability (CWE-1321) in the Quasar framework's utility function 'extend' allows unauthenticated remote attackers to modify the global Object.prototype. This vulnerability can lead to application-level logic bypasses, denial of service, or potentially arbitrary code execution depending on downstream implementation.
A critical connection-level Denial of Service (DoS) vulnerability exists in the Yamux stream multiplexer implementation of py-libp2p (versions <= 0.6.0). The flaw allows unauthenticated or authenticated peers to permanently stall a Yamux multiplexed connection by transmitting a single malformed 12-byte header claiming an oversized payload while withholding the payload bytes.