CVE-2026-13311
ADVISORY - githubSummary
Summary
shell-quote's parse() finalizes its token list with a reduce that uses
Array.prototype.concat as the accumulator. Each prev.concat(arg) copies the entire growing
array, so parse() runs in O(n²) in the number of tokens. An unauthenticated attacker who
can submit a string to any code path that calls parse() on it can block the single-threaded
Node.js event loop for tens of seconds with a small input — a denial of service. The trigger
needs no shell metacharacters (plain space-separated words suffice), so input filters that
only screen for ;, |, $, or backticks do not help.
Root cause
parse.js (lines 200–203), in parseInternal — this path runs on every parse() call:
}).reduce(function (prev, arg) { // finalize parsed arguments
// TODO: replace this whole reduce with a concat
return typeof arg === 'undefined' ? prev : prev.concat(arg);
}, []);
prev.concat(arg) allocates a new array and copies all of prev on every iteration, so
producing an N-token result costs 1 + 2 + … + N = O(N²) copies. A second acc.concat(s)
reduce in the module.exports wrapper (lines 211–224, reached only when env is a function)
has the same shape. The maintainer's own // TODO: replace this whole reduce with a concat
already flags the construct.
Proof of Concept
const { parse } = require('shell-quote');
const ms = fn => { const t = process.hrtime.bigint(); fn(); return Number(process.hrtime.bigint()-t)/1e6; };
for (const N of [16000, 32000, 64000, 128000]) {
console.log(N, 'tokens ->', ms(() => parse('x '.repeat(N))).toFixed(0), 'ms');
}
Measured on shell-quote@1.8.4, Node v24:
| input (N tokens) | bytes | parse() |
ratio vs prev (2× input) |
|---|---|---|---|
| 16 000 | 32 KB | 678 ms | — |
| 32 000 | 64 KB | 4 169 ms | ×6.2 |
| 64 000 | 128 KB | 14 914 ms | ×3.6 |
| 128 000 | 256 KB | 57 319 ms | ×3.8 |
Time grows ~×4 per 2× input → confirmed O(n²). A ~128 KB input blocks the event loop ~15 s; ~256 KB → ~57 s; a few hundred KB more → minutes. poc.js
Impact
parse() is synchronous on the main thread; while it copies arrays quadratically the entire
event loop is blocked and the process serves no other requests. Any service that calls parse()
on attacker-influenced input (command parsers, chat-ops / bot command handlers, REPLs,
build-script / arg-string splitters) can be driven to a sustained DoS with a single small
request. No code execution and no data disclosure — availability only.
End-to-end confirmation: a minimal HTTP server that calls parse() on the request body, hit
with one POST of 'x '.repeat(32000) (~63 KB), froze for ~4.5 s. An out-of-process probe
client issuing harmless GET /ping requests (normally ~1 ms) observed 27 consecutive pings
stalled by up to 4374 ms during that single request — i.e. every concurrent client was denied
service for the whole parse. Scaling the body to a few hundred KB extends the outage to minutes.
This is the same class as several accepted 2026 advisories for quadratic-parser DoS on
untrusted input (e.g. markdown-it CVE-2026-48988, js-yaml CVE-2026-53550,
python-multipart CVE-2026-53539). It is distinct from the known shell-quote
command-injection issues (CVE-2021-42740, CVE-2016-10541, CVE-2026-9277), which are all in
quote(), not parse().
Suggested remediation
Replace the O(n²) concat-in-reduce with a linear flatten that pushes into the
accumulator instead of reallocating and copying it on every iteration. Apply the
same shape to the wrapper's acc.concat(s) reduce. A defensive input-length cap on
parse() is a cheap additional stop-gap.
Maintainer note (edit): the originally-suggested
Array.prototype.flat()is ES2019 / Node 11+, butshell-quotedeclaresengines: node >= 0.4, so.flat()would silently drop support for older runtimes. The fix instead flattens one-level array tokens withforEach/push— and deliberately notpush.apply(...), since spreading a large array into function arguments can exceed the engine's argument count limit. Output is byte-identical to the current code across strings,undefinedholes, one-level array tokens, and{op}/{comment}/{op:'glob'}objects, and finalizing is now linear (1,024,000 tokens in ~150 ms vs ~57 s for 128,000 before). Thanks for the clear report and PoC — the analysis and reproduction were spot on.
Disclosure
Found by source audit + wall-clock confirmation against 1.8.4 (and verified the same code is
present on main). Reported privately here; no public disclosure until a fix is available.
Common Weakness Enumeration (CWE)
Inefficient Algorithmic Complexity
Excessive Platform Resource Consumption within a Loop
GitHub
3.9
CVSS SCORE
8.7high| Package | Type | OS Name | OS Version | Affected Ranges | Fix Versions |
|---|---|---|---|---|---|
| shell-quote | npm | - | - | <=1.8.4 | 1.9.0 |
CVSS:4 Severity and metrics
The CVSS metrics represent different qualitative aspects of a vulnerability that impact the overall score, as defined by the CVSS Specification.
The vulnerable component is bound to the network stack, but the attack is limited at the protocol level to a logically adjacent topology. This can mean an attack must be launched from the same shared physical (e.g., Bluetooth or IEEE 802.11) or logical (e.g., local IP subnet) network, or from within a secure or otherwise limited administrative domain (e.g., MPLS, secure VPN to an administrative network zone). One example of an Adjacent attack would be an ARP (IPv4) or neighbor discovery (IPv6) flood leading to a denial of service on the local LAN segment (e.g., CVE-2013-6014).
Specialized access conditions or extenuating circumstances do not exist. An attacker can expect repeatable success when attacking the vulnerable component.
The successful attack does not depend on the deployment and execution conditions of the vulnerable system. The attacker can expect to be able to reach the vulnerability and execute the exploit under all or most instances of the vulnerability.
The attacker is unauthenticated prior to attack, and therefore does not require any access to settings or files of the vulnerable system to carry out an attack.
The vulnerable system can be exploited without interaction from any human user, other than the attacker. Examples include: a remote attacker is able to send packets to a target system a locally authenticated attacker executes code to elevate privileges.
There is no loss of confidentiality within the Vulnerable System.
There is no loss of confidentiality within the Subsequent System or all confidentiality impact is constrained to the Vulnerable System.
There is no loss of integrity within the Vulnerable System.
There is no loss of integrity within the Subsequent System or all integrity impact is constrained to the Vulnerable System.
There is a total loss of availability, resulting in the attacker being able to fully deny access to resources in the Vulnerable System; this loss is either sustained (while the attacker continues to deliver the attack) or persistent (the condition persists even after the attack has completed). Alternatively, the attacker has the ability to deny some availability, but the loss of availability presents a direct, serious consequence to the Vulnerable System (e.g., the attacker cannot disrupt existing connections, but can prevent new connections; the attacker can repeatedly exploit a vulnerability that, in each instance of a successful attack, leaks a only small amount of memory, but after repeated exploitation causes a service to become completely unavailable).
There is no impact to availability within the Subsequent System or all availability impact is constrained to the Vulnerable System.
Debian
-
Ubuntu
-
CVSS SCORE
N/AmediumRed Hat
2.8
CVSS SCORE
6.5mediumminimos
MINI-2gm8-23c5-9pgp
-
minimos
MINI-3m6j-x6q5-p866
-
minimos
MINI-4r84-m755-cx8j
-
minimos
MINI-5x62-fmr3-4qj6
-
minimos
MINI-8gpv-rqqp-r73h
-
minimos
MINI-hwx8-r398-gmqg
-
minimos
MINI-prvc-vgv8-63w2
-