CVE-2026-13311: shell-quote Denial of Service Vulnerability

HIGH
Advisory Identifier: GHSA-395f-4hp3-45gv
CVSS 7.5
CWE-407
Published
Advisory Summary

CVE-2026-13311 is a HIGH (CVSS 7.5) denial of service vulnerability (CWE-407: Inefficient Algorithmic Complexity) affecting shell-quote libraries in the npm ecosystem. The issue involves quadratic-complexity denial of service in `parse()` (cwe-407). Remediation is available by upgrading affected packages to version 1.9.0.

CVE-2026-13311 at a Glance

CVE Identifier CVE-2026-13311
GHSA Identifier GHSA-395f-4hp3-45gv
Severity HIGH
CVSS v3.1 Base Score 7.5
CWE Classification CWE-407: Inefficient Algorithmic Complexity
Affected Product shell-quote
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 7.5

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity None (I:N)
Availability High (A:H)

How to Fix CVE-2026-13311

To remediate this vulnerability, upgrade all affected shell-quote dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

shell-quote fixed: 1.9.0
npm install shell-quote@1.9.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls shell-quote

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2026-13311, inspect your project dependencies using npm:

npm ls shell-quote

Analyze the terminal output to locate any version of shell-quote in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.9.0), your application is affected.

Note: The npm ls shell-quote command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-407: Inefficient Algorithmic Complexity, where an algorithm in a product has an inefficient worst-case computational complexity that may be detrimental to system performance and can be triggered by an attacker, typically using crafted manipulations that ensure that the worst case is being reached. In shell-quote, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### 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](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.

CVE-2026-13311 Affected shell-quote Versions

1 component(s)
Fixed Versions:
1.9.0
Product / Package Ecosystem Affected Versions Fixed Version
shell-quote
npm
< 1.9.0
1.9.0

References

Frequently Asked Questions

What is CVE-2026-13311?
CVE-2026-13311 is a security vulnerability in shell-quote involving Quadratic-complexity Denial of Service in `parse()` (CWE-407) under CWE-407: Inefficient Algorithmic Complexity.
Which shell-quote versions are affected?
Affected versions include releases prior to 1.9.0 across affected packages: shell-quote.
What version fixes CVE-2026-13311?
Upgrading to 1.9.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is shell-quote affected?
Yes, shell-quote is affected in releases prior to 1.9.0. Upgrade your dependency to 1.9.0 or later.
What is the CVSS score of CVE-2026-13311?
CVE-2026-13311 has a CVSS v3.1 base score of 7.5 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H.
What CWE is associated with CVE-2026-13311?
CVE-2026-13311 is classified under CWE-407: Inefficient Algorithmic Complexity (An algorithm in a product has an inefficient worst-case computational complexity that may be detrimental to system performance and can be triggered by an attacker, typically using crafted manipulations that ensure that the worst case is being reached.).
How can I check npm dependencies for CVE-2026-13311?
Run the dependency tree command: 'npm ls shell-quote'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-395f-4hp3-45gv",
  "modified" : "2026-07-20T21:49:34Z",
  "published" : "2026-07-20T21:49:34Z",
  "aliases" : [ "CVE-2026-13311" ],
  "summary" : "shell-quote: Quadratic-complexity Denial of Service in `parse()` (CWE-407)",
  "details" : "### Summary\n`shell-quote`'s `parse()` finalizes its token list with a `reduce` that uses\n`Array.prototype.concat` as the accumulator. Each `prev.concat(arg)` copies the entire growing\narray, so `parse()` runs in **O(n²)** in the number of tokens. An unauthenticated attacker who\ncan submit a string to any code path that calls `parse()` on it can block the single-threaded\nNode.js event loop for tens of seconds with a small input — a denial of service. The trigger\nneeds **no shell metacharacters** (plain space-separated words suffice), so input filters that\nonly screen for `;`, `|`, `$`, or backticks do not help.\n\n### Root cause\n`parse.js` (lines 200–203), in `parseInternal` — this path runs on **every** `parse()` call:\n\n```js\n}).reduce(function (prev, arg) { // finalize parsed arguments\n    // TODO: replace this whole reduce with a concat\n    return typeof arg === 'undefined' ? prev : prev.concat(arg);\n}, []);\n```\n\n`prev.concat(arg)` allocates a new array and copies all of `prev` on every iteration, so\nproducing an N-token result costs `1 + 2 + … + N = O(N²)` copies. A second `acc.concat(s)`\nreduce in the `module.exports` wrapper (lines 211–224, reached only when `env` is a function)\nhas the same shape. The maintainer's own `// TODO: replace this whole reduce with a concat`\nalready flags the construct.\n\n### Proof of Concept\n```js\nconst { parse } = require('shell-quote');\nconst ms = fn => { const t = process.hrtime.bigint(); fn(); return Number(process.hrtime.bigint()-t)/1e6; };\nfor (const N of [16000, 32000, 64000, 128000]) {\n  console.log(N, 'tokens ->', ms(() => parse('x '.repeat(N))).toFixed(0), 'ms');\n}\n```\n\nMeasured on `shell-quote@1.8.4`, Node v24:\n\n| input (N tokens) | bytes  | `parse()` | ratio vs prev (2× input) |\n|-----------------:|-------:|----------:|:------------------------:|\n| 16 000           | 32 KB  |    678 ms | —                        |\n| 32 000           | 64 KB  |  4 169 ms | ×6.2                     |\n| 64 000           | 128 KB | 14 914 ms | ×3.6                     |\n| 128 000          | 256 KB | **57 319 ms** | ×3.8                 |\n\nTime grows ~×4 per 2× input → confirmed O(n²). A ~128 KB input blocks the event loop ~15 s;\n~256 KB → ~57 s; a few hundred KB more → minutes.\n<img width=\"656\" height=\"214\" alt=\"image\" src=\"https://github.com/user-attachments/assets/e8955b0e-0527-45ca-94b7-c3a2d8c0c82e\" />\n[poc.js](https://github.com/user-attachments/files/29255995/poc.js)\n\n### Impact\n`parse()` is synchronous on the main thread; while it copies arrays quadratically the entire\nevent loop is blocked and the process serves no other requests. Any service that calls `parse()`\non attacker-influenced input (command parsers, chat-ops / bot command handlers, REPLs,\nbuild-script / arg-string splitters) can be driven to a sustained DoS with a single small\nrequest. No code execution and no data disclosure — availability only.\n\nEnd-to-end confirmation: a minimal HTTP server that calls `parse()` on the request body, hit\nwith **one** `POST` of `'x '.repeat(32000)` (~63 KB), froze for ~4.5 s. An out-of-process probe\nclient issuing harmless `GET /ping` requests (normally ~1 ms) observed **27 consecutive pings\nstalled by up to 4374 ms** during that single request — i.e. every concurrent client was denied\nservice for the whole parse. Scaling the body to a few hundred KB extends the outage to minutes.\n\nThis is the same class as several accepted 2026 advisories for quadratic-parser DoS on\nuntrusted input (e.g. markdown-it CVE-2026-48988, js-yaml CVE-2026-53550,\npython-multipart CVE-2026-53539). It is **distinct** from the known `shell-quote`\ncommand-injection issues (CVE-2021-42740, CVE-2016-10541, CVE-2026-9277), which are all in\n`quote()`, not `parse()`.\n\n### Suggested remediation\nReplace the O(n²) concat-in-reduce with a linear flatten that **pushes into the\naccumulator** instead of reallocating and copying it on every iteration. Apply the\nsame shape to the wrapper's `acc.concat(s)` reduce. A defensive input-length cap on\n`parse()` is a cheap additional stop-gap.\n\n> **Maintainer note (edit):** the originally-suggested `Array.prototype.flat()` is\n> ES2019 / Node 11+, but `shell-quote` declares `engines: node >= 0.4`, so `.flat()`\n> would silently drop support for older runtimes. The fix instead flattens one-level\n> array tokens with `forEach`/`push` — and deliberately not `push.apply(...)`, since\n> spreading a large array into function arguments can exceed the engine's argument\n> count limit. Output is byte-identical to the current code across strings, `undefined`\n> holes, one-level array tokens, and `{op}`/`{comment}`/`{op:'glob'}` objects, and\n> finalizing is now linear (1,024,000 tokens in ~150 ms vs ~57 s for 128,000 before).\n> Thanks for the clear report and PoC — the analysis and reproduction were spot on.\n\n### Disclosure\nFound by source audit + wall-clock confirmation against 1.8.4 (and verified the same code is\npresent on `main`). Reported privately here; no public disclosure until a fix is available.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
  }, {
    "type" : "CVSS_V4",
    "score" : "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"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "shell-quote"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "1.9.0"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 1.8.4"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/ljharb/shell-quote/security/advisories/GHSA-395f-4hp3-45gv"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-13311"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/ljharb/shell-quote/commit/7ff5488599d01c323514f02f5efb74088dd134ec"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/ljharb/shell-quote"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/ljharb/shell-quote/releases/tag/v1.9.0"
  }, {
    "type" : "WEB",
    "url" : "https://www.npmjs.com/package/shell-quote"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-407" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-20T21:49:34Z",
    "nvd_published_at" : "2026-06-25T05:16:52Z"
  }
}