Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
📦 npm
Not in CISA KEV

CVE-2026-13311 — shell-quote

Fix: ljharb/shell-quote@7ff5488

CVE-2026-13311 is a CWE-407 vulnerability in shell-quote. A fix is available for shell-quote — see the affected versions and patch details below.

shell-quote parse() is quadratic in token count, enabling denial of service

Also known asGHSA-395f-4hp3-45gv
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 10, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-13311.

EPSS Exploitation Probability

via FIRST.org ↗
0.4%probability of exploitation in next 30 days
Lower Risk-0.24%
Lower risk than most CVEs28th percentile — riskier than 28% of all scored CVEsHighest risk
0.00%0.37%0.73%1.10%0.4%0.4%0.6%0.4%Aug 26Oct 26Oct 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

Real-World Exposure

1 pkg affected

How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.

2Kother npm packages depend on this — each one inherits the vulnerability until it's patched upstream
shell-quotenpm
78.6Mdownloads / week

Description

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 [email protected], Node v24:

input (N tokens)bytesparse()ratio vs prev (2× input)
16 00032 KB678 ms—
32 00064 KB4 169 ms×6.2
64 000128 KB14 914 ms×3.6
128 000256 KB57 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.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
📦npmshell-quoteall versions1.9.0npm install shell-quote@1.9.0

Affected Products

1 product · 1 configurations
Application
shell-quoteshell-quote_project
< 1.9.0
range

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for shell-quote, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update shell-quote to 1.9.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-13311 is resolved across your whole dependency graph.

  3. Workarounds

    Cap what an attacker can consume: apply request size, rate and timeout limits in front of the affected component, and run it with memory and CPU limits so exhaustion degrades one worker rather than the whole service.

Fixing This On Your OS

If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.

Red HatModerate

A flaw was found in the shell-quote npm package's `parse()` function, which uses an `O(n²)` array concatenation pattern when processing tokens. An attacker who can supply untrusted input to code paths calling `parse()` can block the Node.js event loop with a relatively small payload of space-separated words, causing a…

Workaround published by Red Hat
Upgrade shell-quote to version 1.8.5. If upgrading shell-quote to version 1.8.5 or later is not immediately possible, the following mitigations can reduce exposure: 1. Validate and limit the length of any user-controlled strings before passing them to shell-quote's `parse()` function. 2. If `parse()` is not required, ensure that only the `quote()` function is used, as it is not affected by this vulnerability.
Source: Red Hat security advisory for CVE-2026-13311 (CC BY 4.0)
ProductFixed inAdvisory
Red Hat OpenShift AI 3.4rhoai/odh-mod-arch-automl-rhel9:1787250508RHSA-2026:60520

Frequently Asked Questions

### 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 backt
O3 Security · Impact-Aware SCA

Is CVE-2026-13311 in your dependencies?

Find it across npm, including transitive dependencies.

CVE-2026-13311: shell-quote DoS — Fixed in 1.9.0