CVE-2026-13311: shell-quote parse() is quadratic in token count, enabling denial of service
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:
js }).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 js 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. <img width="656" height="214" alt="image" src="https://github.com/user-attachments/assets/e8955b0e-0527-45ca-94b7-c3a2d8c0c82e" /> 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+, but shell-quote declares engines: node >= 0.4, so .flat() would silently drop support for older runtimes. The fix instead flattens one-level array tokens with forEach/push — and deliberately not push.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, undefined holes, 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.
Other sources
shell-quote prior to 1.8.5 finalizes parsed tokens in parse() using Array.prototype.concat as a reduce accumulator, which reallocates and copies the entire growing array on every iteration. As a result parse() runs in O(n^2) time relative to the number of input tokens. An attacker who can supply an attacker-controlled string to any code path that calls parse() (no shell metacharacters are required; plain space-separated words suffice) can block the single-threaded Node.js event loop for an extended period with a small input, resulting in a denial of service. There is no code execution or data disclosure; impact is to availability only. Fixed in 1.8.5.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/shell-quoteto a version that resolves this vulnerability.Fixed in 1.9.0 - Upgrade
Upgrade
shell-quoteto a version that resolves this vulnerability.Fixed in 1.8.5 - Compensating control
Apply an attacker-controlled input-length cap for any request/body or string passed into shell-quote's parse(), since a ~128 KB input blocks the single-threaded Node.js event loop for ~15 seconds (larger inputs can block for tens of seconds to minutes).
- Operational
If any attacker-supplied requests were sent that caused event-loop blocking, treat the service as unavailable for that window and ensure you validate availability/health recovery after applying the fix (the issue is availability-only).
Event History
Frequently Asked Questions
What is the severity of CVE-2026-13311?
CVE-2026-13311 has a severity rating of 7.5, categorized as high.
How does CVE-2026-13311 affect systems using shell-quote?
CVE-2026-13311 allows an attacker to exploit the quadratic time complexity of the parse() function, leading to a denial of service.
What is the impact of CVE-2026-13311 on application performance?
CVE-2026-13311 can severely degrade application performance due to the inefficient handling of token parsing.
How can I mitigate the risks associated with CVE-2026-13311?
To mitigate CVE-2026-13311, update to shell-quote version 1.8.5 or later.
Is CVE-2026-13311 an issue for all versions of shell-quote?
Yes, CVE-2026-13311 affects all versions of shell-quote prior to 1.8.5.