---
title: "GHSA-395f-4hp3-45gv: shell-quote: Quadratic-complexity Denial of Service in `parse()` (CWE-407)"
description: "High severity. shell-quote: Quadratic-complexity Denial of Service in `parse()` (CWE-407). Affects shell-quote; fixed in 1.9.0."
url: https://inlinr.com/advisories/GHSA-395f-4hp3-45gv
lang: en
---

[Packages](https://inlinr.com/packages) / [shell-quote](https://inlinr.com/packages/shell-quote) / [Security](https://inlinr.com/packages/shell-quote/security)

High Vulnerability GHSA-395f-4hp3-45gv CVE-2026-13311 Published 20 Jul 2026

# shell-quote: Quadratic-complexity Denial of Service in \`parse()\` (CWE-407)

Your version 

Affected releases · [shell-quote](https://inlinr.com/packages/shell-quote)

- 1.x 1.0.0 – 1.8.4 Fixed in 1.9.0
- 0.1.x and older 0.0.0 – 0.1.1 No fix, no longer updated

On 1.x, the newest line:

```
npm install shell-quote@1.9.0
```

Every release on a timeline

1.x 

1. Affected

1.0.0 – 1.8.4  
26 releases
2. Fixed

1.9.0 – 1.12.0  
4 releases, latest

0.x 

1. Affected

0.0.0 – 0.1.1  
4 releases

34 stable releases of shell-quote, oldest to newest, one row per major.

## What happens

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](https://github.com/user-attachments/files/29255995/poc.js "https://github.com/user-attachments/files/29255995/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.

## How it is exploited

Exploitable over the network, with no account and no action from a user.

- **Confidentiality**: none
- **Integrity**: none
- **Availability**: high

CVSS:3.1/​AV:N/​AC:L/​PR:N/​UI:N/​S:U/​C:N/​I:N/​A:H

## References

- [Reference](https://github.com/ljharb/shell-quote/security/advisories/GHSA-395f-4hp3-45gv "https://github.com/ljharb/shell-quote/security/advisories/GHSA-395f-4hp3-45gv")  
github.com/ljharb/shell-quote/security/advisori…
- [NVD](https://nvd.nist.gov/vuln/detail/CVE-2026-13311 "https://nvd.nist.gov/vuln/detail/CVE-2026-13311")  
nvd.nist.gov/vuln/detail/CVE-2026-13311
- [Fix commit](https://github.com/ljharb/shell-quote/commit/7ff5488599d01c323514f02f5efb74088dd134ec "https://github.com/ljharb/shell-quote/commit/7ff5488599d01c323514f02f5efb74088dd134ec")  
7ff5488
- [Package](https://github.com/ljharb/shell-quote "https://github.com/ljharb/shell-quote")  
github.com/ljharb/shell-quote
- [Reference](https://github.com/ljharb/shell-quote/releases/tag/v1.9.0 "https://github.com/ljharb/shell-quote/releases/tag/v1.9.0")  
github.com/ljharb/shell-quote/releases/tag/v1.9…
- [Reference](https://www.npmjs.com/package/shell-quote "https://www.npmjs.com/package/shell-quote")  
npmjs.com/package/shell-quote
